Home > Article > Backend Development > Python multi-threaded programming 3: Using mutex locks to synchronize threads
Question raised
In the example in the previous section, each thread is independent of each other and has no relationship with each other. Now assume an example: there is a global count num, each thread gets this global count, performs some processing based on num, and then adds 1 to num. It is easy to write code like this:
# encoding: UTF-8 import threading import time class MyThread(threading.Thread): def run(self): global num time.sleep(1) num = num+1 msg = self.name+' set num to '+str(num) print msg num = 0 def test(): for i in range(5): t = MyThread() t.start() if __name__ == '__main__': test()
But the running result is incorrect:
Thread-5 set num to 2
Thread-3 set num to 3
Thread-2 set num to 5
Thread-1 set num to 5
Thread-4 set num to 4
The reason for the problem is that the access of multiple threads to the same resource is not controlled, causing damage to the data and making the results of thread operation unpredictable. . This phenomenon is called "thread unsafe".
Mutex lock synchronization
The above example leads to the most common problem of multi-threaded programming: data sharing. When multiple threads modify a certain shared data, synchronization control is required.
Thread synchronization can ensure that multiple threads safely access competing resources. The simplest synchronization mechanism is to introduce a mutex lock. A mutex introduces a state to a resource: locked/unlocked. When a thread wants to change shared data, it must first lock it. At this time, the status of the resource is "locked" and other threads cannot change it; until the thread releases the resource and changes the status of the resource to "unlocked", other threads can Lock the resource again. The mutex lock ensures that only one thread performs writing operations at a time, thereby ensuring the correctness of data in multi-threaded situations.
The Lock class is defined in the threading module, which can handle locking conveniently:
#Create lock
mutex = threading.Lock()
#Lock
mutex.acquire([timeout])
#Release
mutex.release()
Among them, the lock method acquire can have an optional parameter timeout with a timeout period. If a timeout is set, the return value can be used to determine whether the lock is obtained after the timeout, so that some other processing can be performed.
The code to implement the above example using a mutex is as follows:
import threading import time class MyThread(threading.Thread): def run(self): global num time.sleep(1) if mutex.acquire(1): num = num+1 msg = self.name+' set num to '+str(num) print msg mutex.release() num = 0 mutex = threading.Lock() def test(): for i in range(5): t = MyThread() t.start() if __name__ == '__main__': test()
Running results:
Thread-3 set num to 1
Thread-4 set num to 2
Thread-5 set num to 3
Thread-2 set num to 4
Thread-1 set num to 5
You can see that after adding the mutex lock, the running results are in line with expectations.
Synchronous blocking
When a thread calls the acquire() method of the lock to acquire the lock, the lock enters the "locked" state. Only one thread can obtain the lock at a time. If another thread attempts to obtain the lock at this time, the thread will become "blocked", which is called "synchronous blocking" (see the basic concept of multi-threading).
Until the thread owning the lock calls the release() method of the lock to release the lock, the lock enters the "unlocked" state. The thread scheduler selects one of the threads in the synchronous blocking state to obtain the lock and makes the thread enter the running state.
The most basic contents of mutex locks are these. The next section will discuss reentrant locks (RLock) and deadlock issues.