Python Semaphore Implementation Challenge
This challenge involves creating a basic semaphore mechanism in Python. Semaphores are synchronization primitives used to control access to a shared resource by multiple threads. Implementing one helps understand fundamental concurrency control concepts.
Problem Description
Your task is to create a Python class named Semaphore that mimics the behavior of a standard semaphore. This semaphore should manage a counter representing the number of available permits. Threads will attempt to acquire a permit (decrementing the counter) and release a permit (incrementing the counter). If no permits are available when a thread tries to acquire one, that thread should block until a permit is released.
Key Requirements:
- The
Semaphoreclass should accept an initialvalue(number of permits) upon instantiation. - It must provide two primary methods:
acquire(self): Attempts to acquire a permit. Ifvalueis greater than 0, it decrementsvalueand returnsTrue. Ifvalueis 0, it blocks the calling thread untilvaluebecomes greater than 0, then decrements and returnsTrue.release(self): Increments thevalue. This operation should unblock any threads waiting onacquire.
- The implementation must be thread-safe to prevent race conditions.
Expected Behavior:
- Multiple threads can call
acquireconcurrently. - Only a limited number of threads (equal to the initial
value) can successfullyacquirepermits simultaneously. - Threads attempting to
acquirewhen no permits are available should wait gracefully. releaseshould correctly signal waiting threads.
Edge Cases:
- Initializing the semaphore with a
valueof 0. - Releasing a semaphore that has no threads waiting on it.
- Multiple threads calling
releaserapidly.
Examples
Example 1:
import threading
import time
def worker(semaphore, worker_id):
print(f"Worker {worker_id}: Trying to acquire semaphore.")
semaphore.acquire()
print(f"Worker {worker_id}: Acquired semaphore. Working...")
time.sleep(1) # Simulate work
print(f"Worker {worker_id}: Releasing semaphore.")
semaphore.release()
# Initializing semaphore with 2 permits
semaphore = Semaphore(2)
threads = []
for i in range(5):
thread = threading.Thread(target=worker, args=(semaphore, i))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
print("All workers finished.")
Output (order of "Acquired semaphore" and "Releasing semaphore" might vary slightly due to thread scheduling):
Worker 0: Trying to acquire semaphore.
Worker 0: Acquired semaphore. Working...
Worker 1: Trying to acquire semaphore.
Worker 1: Acquired semaphore. Working...
Worker 2: Trying to acquire semaphore.
Worker 3: Trying to acquire semaphore.
Worker 4: Trying to acquire semaphore.
Worker 0: Releasing semaphore.
Worker 2: Acquired semaphore. Working...
Worker 1: Releasing semaphore.
Worker 3: Acquired semaphore. Working...
Worker 2: Releasing semaphore.
Worker 4: Acquired semaphore. Working...
Worker 3: Releasing semaphore.
Worker 4: Releasing semaphore.
All workers finished.
Explanation: Five worker threads try to acquire a semaphore initialized with 2 permits. Only two workers can acquire the semaphore at a time. As workers finish and release their permits, waiting workers are allowed to proceed.
Example 2:
import threading
import time
def producer(semaphore, item_count):
print("Producer: Starting.")
for i in range(item_count):
print(f"Producer: Producing item {i}. Waiting to acquire semaphore.")
semaphore.acquire()
print(f"Producer: Acquired semaphore. Added item {i} to buffer.")
time.sleep(0.5) # Simulate adding to buffer
print(f"Producer: Releasing semaphore after adding item {i}.")
semaphore.release()
print("Producer: Finished producing.")
# Initializing semaphore with 1 permit (simulating a single-slot buffer)
semaphore = Semaphore(1)
producer_thread = threading.Thread(target=producer, args=(semaphore, 3))
producer_thread.start()
producer_thread.join()
print("Producer challenge finished.")
Output:
Producer: Starting.
Producer: Producing item 0. Waiting to acquire semaphore.
Producer: Acquired semaphore. Added item 0 to buffer.
Producer: Releasing semaphore after adding item 0.
Producer: Producing item 1. Waiting to acquire semaphore.
Producer: Acquired semaphore. Added item 1 to buffer.
Producer: Releasing semaphore after adding item 1.
Producer: Producing item 2. Waiting to acquire semaphore.
Producer: Acquired semaphore. Added item 2 to buffer.
Producer: Releasing semaphore after adding item 2.
Producer: Finished producing.
Producer challenge finished.
Explanation: The producer thread adds items to a conceptual buffer with a capacity of 1, enforced by the semaphore. It must acquire a permit before adding an item and release it afterward, ensuring only one item is processed at a time.
Constraints
- The
Semaphoreclass must be implemented using Python's standardthreadingmodule primitives (e.g.,LockandCondition). Do not usethreading.Semaphoreor any other pre-built semaphore classes. - The initial
valueof the semaphore must be a non-negative integer. - The solution should be efficient and avoid busy-waiting.
Notes
- You will need to manage the internal
value(number of permits) and use synchronization primitives to ensure thread safety and blocking behavior. - Consider how to signal waiting threads when a permit is released. A
threading.Conditionobject is a good candidate for this. - Think about the order of operations within
acquireandreleaseto correctly handle the counter and waiting threads. - This challenge is about understanding the internal workings of synchronization primitives.