I highly recommend that you check out the following article to get a quick idea about different terms used in operating systems with regard to concurrency, multitasking and threading. Back to our topic, In this short post, I am going to demonstrate the use of mutex in python. Let us get started.
What on earth is a mutex ? In operating systems terminology, a mutex (derived from the words "mutual exclusion") is a synchronization primitive. I know that is not a cool definition for beginners so let us describe it using a real life example. Can we eat a cake before it is baked ? maybe but there is a chance we gonna mess things up. Usually, we wait until the food is cooked then we eat it. This analogy is not far from synchronization primitives (ex. mutex). In a modern operating system, typically, we have an application (process) with multiple threads of execution or subtasks. We want the threads to run simultaneously without interfering, otherwise; all sorts of nasty bugs happen. If one thread is computing a value and the other thread is waiting to consume that value, then proper synchronization is required. The famous example is a bank account shared between two threads. One thread deposits to the account and the other one withdraws from it. If these two threads are not synchronized then a race condition may happen and the end result people getting fired.
Python mutex vs lock
We should be very careful not to confuse terminology with programming language syntax. Lock is a generic term. It is a mechanism to implement mutual exclusion to protect shared resources between processes or threads. A lock and a mutex are often used interchangeably however in Python the mutex is a module that defines a class to allow mutual exclusion by acquiring and releasing a lock object. Please note that this class is deprecated (in Python 3.0) and should be avoided. We will see later an alternative Python syntax (threading.Lock) to implement mutual exclusion.
Python mutex example
Given two threads of execution where each one can run for a random amount of time. Since the threads are running simultaneously, there is no way we can control which one is going to run first and which one is going to run next. Also, we want the first thread to finish first and the second thread to wait until the first thread is done. Let us see how to ensure the order of execution using a mutex.
# Import the required modules import threading, time, random # First thread class. Note that we are inheriting # from the Thread class class thread_one(threading.Thread): # A thread has a run method that needs to # be implemented def run(self): print ("The first thread is now sleeping") time.sleep(random.randint(1, 5)) print("First thread has finished") # Second thread class class thread_two(threading.Thread): def run(self): print ("The second thread is now sleeping") time.sleep(random.randint(1, 5)) print("Second thread has finished") # Define two threads t1 = thread_one() t2 = thread_two() # Start threads t1.start() t2.start()
Since each thread sleeps a random amount of time, there is no guarantee that the first thread is going to finish first. If that happens, this leads to a race condition which may crash the application if the two threads happen to manipulate a shared resource or variable.
Python implement mutex
The solution is straightforward. If the second thread finishes before the first thread, it should wait. To do that, we lock the second thread just after it finishes and force it to wait. Once the first thread is done, we unlock or release the second thread. Here is the modified source code
import threading, time, random # Python create mutex my_mutex = threading.Lock() class thread_one(threading.Thread): def run(self): # Python mutex global global my_mutex print ("The first thread is now sleeping") time.sleep(random.randint(1, 5)) print("First thread is finished") # Python release mutex: once the first thread # is done, we release the lock my_mutex.release() class thread_two(threading.Thread): def run(self): # Python mutex global global my_mutex print ("The second thread is now sleeping") time.sleep(random.randint(1, 5)) # Python mutex acquire: second thread has to # to keep waiting until the lock is released my_mutex.acquire() print("Second thread is finished") # Python mutex acquire: main thread is acquiring the lock my_mutex.acquire() t1 = thread_one() t2 = thread_two() t1.start() t2.start()
Please note the following:
- Imagine there is a door at which the second thread has to wait. Just before we start the threads, we lock (using acquire method) the mutex (door) so that the second threads won’t enter. For example, if the second thread runs for one second then there is a chance it won’t wait for the first thread to finish (ex. first thread takes 5 seconds). That is why we lock the door before doing anything (Line 28).
- We used the global keyword (Lines 8, 19) in the run method of each thread because our mutex was declared as a global variable and should be accessed from both threads.
- After the first thread is done, we let the second thread exit the door by releasing the mutex (Line 14).
- The second thread tries to acquire the lock after it finishes all the work (Line 24). Note that the print statement comes right after the acquire statement not before because as long as the thread is waiting, it has not finished yet.
Mutex vs semaphore
Semaphores are also used to synchronize threads of execution so what is the difference between a mutex and a semaphore ? Let us quickly mention what is a semaphore? and do the comparison. A semaphore is another high level synchronization primitive. It restricts the number of simultaneous threads to a shared resource up to a maximum number known as the semaphore count. When a thread requests to access a resource, the semaphore counter is decremented. When the thread finishes, the counter is incremented. In that sense, a mutex is nothing but a binary semaphore meaning there can be only one thread at a time acquiring the mutex. The toilet example when comparing mutex to semaphores is very popular. A mutex is like a key to a toilet. One person at a time can have the key and occupy the toilet. When finished, he gives the key to the next person waiting in line. On the other hand, a semaphore is like having a number of identical toilet keys for several available toilets. The number of keys represents the semaphore count. The count is decremented whenever people come in. When all toilets are occupied, the count becomes zero so no one can use any toilets. If someone leaves, the count is incremented and the next person in line can use that key and so on.
- Mutex is a simple synchronization primitive that can be used to protect a shared resource in a multithreading operating system.
- To create a mutex in Python, import the threading module and use the syntax: mutex = threading.Lock()
- Use the acquire method to lock a mutex and prevent other threads from entering a block of code or accessing a shared variable.
- Use the release method to free a locked resource and allow other threads to have access
- A mutex can be thought of as a binary semaphore (count = 1).
This article is not the best place to explain synchronization primitives in details. The main goal was to demonstrate how to use a mutex in Python. For more information you can refer to the following:
That is it for today. Please leave a comment or ask questions in the comment section below. Thanks for reading.Search Terms...