Controlling Concurrency Limits with Gevent Pools in Python
Controlling concurrant execution in Python with gevent requires applying monkey patches before importing standard blocking modules. This replaces synchronous socket and I/O operations with cooperative counterparts, allowing greenlets to yield control during blocking calls.
from gevent import monkey
monkey.patch_all()
import time
from gevent.pool import Pool
import gevent
CONCURRENCY_LIMIT = 10
WORKER_DELAY = 3.0
TOTAL_JOBS = 25
def process_record(job_id):
"""Simulates a blocking I/O operation."""
time.sleep(WORKER_DELAY)
print(f"Completed job {job_id}")
# Initialize the pool with a hard cap on active greenlets
executor = Pool(CONCURRENCY_LIMIT)
start_time = time.perf_counter()
# Schedule all jobs; the pool automatically queues excess tasks
active_greenlets = [executor.spawn(process_record, idx) for idx in range(TOTAL_JOBS)]
# Block until every scheduled greenlet finishes
gevent.joinall(active_greenlets)
elapsed = time.perf_counter() - start_time
print(f"Total execution time: {elapsed:.2f} seconds")
The Pool object acts as a semaphore for greenlet creation. When executor.spawn() is called, the pool checks the number of currently running greenlets. If the count matches CONCURRENCY_LIMIT, the new task enters an internal queue and waits for a slot to free up. This prevents resource exhaustion when dealing with large batches of network requests or database queries.
Running the script produces output similar to the following:
Completed job 0
Completed job 1
...
Completed job 24
Total execution time: 9.01 seconds
With a limit of 10 concurrent workers and a 3-second delay per task, processing 25 jobs requires three batches. The first two batches handle 10 jobs each (3 seconds per batch), and the final batch processses the remaining 5 jobs. The total runtime reflects this batching behavior, confirming that the concurrency cap is strictly enforced while maintaining asynchronous execution within each batch.