Understanding Concurrency Control in Python: Barrier, Locks, and Semaphores

Understanding Concurrency Control in Python: Barrier, Locks, and Semaphores

Python, with its versatile libraries and built-in modules, offers several mechanisms for managing concurrency, among which are barriers, locks, and semaphores. These tools play a crucial role in synchronizing access to shared resources, preventing race conditions, and ensuring thread safety. Let's delve into each of these constructs to understand their significance and usage in Python.

Barriers

Barriers are synchronization primitives that enable threads to wait for each other to reach a certain point of execution before proceeding. In Python, barriers are implemented using the threading.Barrier class. They are particularly useful in scenarios where a group of threads needs to synchronize their execution, such as performing a computation together or waiting for initialization to complete.

Here's a basic example demonstrating the usage of a barrier:

import threading

def worker(barrier):
    print("Worker thread started")
    barrier.wait()
    print("Worker thread finished")

# Create a barrier for 3 threads
barrier = threading.Barrier(3)

# Create and start worker threads
threads = [threading.Thread(target=worker, args=(barrier,)) for _ in range(3)]

for thread in threads:
    thread.start()

# Wait for all threads to finish
for thread in threads:
    thread.join()
print("All threads have finished")        

In this example, the barrier ensures that all three worker threads reach the barrier.wait() call before any of them can proceed further.

Locks

Locks, also known as mutexes, are used to enforce mutual exclusion, allowing only one thread to access a shared resource at a time. Python provides locks through the threading.Lock class. Locks are crucial for preventing race conditions where multiple threads attempt to modify shared data concurrently, leading to unpredictable behavior.

Let's consider a simple scenario where multiple threads are updating a shared counter:

import threading

counter = 0
lock = threading.Lock()

def increment_counter():
    global counter
    with lock:
        counter += 1

# Create and start threads to increment the counter
threads = [threading.Thread(target=increment_counter) for _ in range(10)]

for thread in threads:
    thread.start()

# Wait for all threads to finish
for thread in threads:
    thread.join()
print("Final counter value:", counter)        

In this example, the lock ensures that only one thread can execute the increment_counter() function at a time, preventing race conditions and ensuring that the counter is updated correctly.

Semaphores

Semaphores are a more generalized version of locks that allow multiple threads to access a shared resource with a limited capacity. Semaphores maintain a counter that represents the number of available resources. Threads can acquire and release resources using the acquire() and release() methods, respectively. Python's threading.Semaphore class provides this functionality.

Let's illustrate the use of semaphores with a scenario where multiple threads are writing to a limited capacity buffer:

import multiprocessing
import time

# Define a function for the first process
def process1(semaphore):
    print("Process 1 is requesting the resource...")
    semaphore.acquire()  # Acquire the semaphore
    print("Process 1 has acquired the resource.")
    time.sleep(2)  # Simulate some work
    semaphore.release()  # Release the semaphore
    print("Process 1 has released the resource.")

# Define a function for the second process
def process2(semaphore):
    print("Process 2 is requesting the resource...")
    semaphore.acquire()  # Acquire the semaphore
    print("Process 2 has acquired the resource.")
    time.sleep(2)  # Simulate some work
    semaphore.release()  # Release the semaphore
    print("Process 2 has released the resource.")

if __name__ == "__main__":
    # Create a semaphore with initial value 1
    semaphore = multiprocessing.Semaphore(1)

    # Create the first process
    p1 = multiprocessing.Process(target=process1, args=(semaphore,))
    # Create the second process
    p2 = multiprocessing.Process(target=process2, args=(semaphore,))

    # Start both processes
    p1.start()
    p2.start()

    # Join both processes to wait for them to finish
    p1.join()
    p2.join()

    print("Both processes have finished.")
           

In this example, we have two processes, process1 and process2, which request access to a shared resource. We create a semaphore with an initial value of 1 to control access to this resource. When a process wants to access the resource, it acquires the semaphore using semaphore.acquire(). If the semaphore count is 0 (indicating the resource is already in use), the process will block until the resource becomes available. Once the process completes its task, it releases the semaphore with semaphore.release(), allowing other processes to acquire it.


Thanks for reading.

Kavyansh :-)

?? Kartikey Kumar

Senior Software Engineer | GenAI | Freelancer | AWS | Python | NodeJs | ExpressJS | Machine Learning |

11 个月

greate sharing, do not forget live saviour threading.Condition() when we have conditions

要查看或添加评论,请登录

Kavyansh Pandey的更多文章

  • ?? Essential AWS Services Every Dev Should Know ??

    ?? Essential AWS Services Every Dev Should Know ??

    ?? AWS EC2 (Elastic Compute Cloud): AWS EC2 provides virtual servers in the cloud, granting developers scalable compute…

其他会员也浏览了