Skillversitas

Semaphore, Bounded Semaphore and RWLock

Beyond the plain lock, Python provides semaphores to limit how many threads may enter a region at once, and reader-writer locks (not in the standard library) to allow many concurrent readers but exclusive writers. These two primitives cover the classic "resource pool" and "cached data" patterns.

Semaphore

A semaphore starts with a counter n. Each acquire() decrements it, each release() increments it; when the counter reaches zero, further acquires block. The standard use is capping concurrency - for example, at most 5 workers hitting an API that rate-limits at 5 requests per second:

import threading
import time

sem = threading.Semaphore(5)

def fetch(url):
    with sem:                    # max 5 in flight
        print("fetching", url)
        time.sleep(1)

threads = [threading.Thread(target=fetch, args=(f"/page/{i}",)) for i in range(20)]
[t.start() for t in threads]
[t.join() for t in threads]

Bounded Semaphore

A BoundedSemaphore(n) additionally raises ValueError if you release more times than you acquired. That turns a logic bug (a code path that releases without acquiring) into an immediate error instead of a semaphore that silently admits more threads than the limit:

sem = threading.BoundedSemaphore(1)
sem.acquire()
sem.release()
sem.release()   # ValueError: release without acquire

Reader-Writer Lock

The standard library has no RWLock, but the pattern is simple with a lock and two conditions: any number of readers proceed together, while a writer needs the lock alone. A compact correct version:

import threading

class RWLock:
    def __init__(self):
        self._lock = threading.Lock()
        self._readers = 0

    def acquire_read(self):
        with self._lock:
            self._readers += 1

    def release_read(self):
        with self._lock:
            self._readers -= 1

    def acquire_write(self):
        self._lock.acquire()

    def release_write(self):
        self._lock.release()

Readers count themselves under the lock so a writer arriving mid-read waits for the current cohort to finish. In practice, use this shape for in-memory caches and config tables that are read far more often than they change.

Summary

Use a semaphore to bound the number of concurrent users of a scarce resource, a bounded semaphore when release bugs should fail loudly, and a reader-writer pattern when reads vastly outnumber writes and you want parallel reads.