本文介绍了竞争条件以及如何使用 Python中threading
模块中的Lock
对象来防止竞争的出现。
什么是条件竞争
当两个线程试图同时访问一个共享变量时,就会出现条件竞争。
第一个线程从共享变量中读取值,第二个线程也从同一个共享变量中读取值。
然后两个线程都尝试更改共享变量的值,他们竞相查看哪个线程最后向变量写入值。
最后写入共享变量的线程的值被保留,因为它覆盖了前一个线程写入的值。
竞争示例条件
以下示例说明了条件竞争:
from threading import Thread |
|
from time import sleep |
|
counter = 0 |
|
def increase(by): |
|
global counter |
|
local_counter = counter |
|
local_counter += by |
|
sleep(0.1) |
|
counter = local_counter |
|
print(f'counter={counter}') |
|
# create threads |
|
t1 = Thread(target=increase, args=(10,)) |
|
t2 = Thread(target=increase, args=(20,)) |
|
# start the threads |
|
t1.start() |
|
t2.start() |
|
# wait for the threads to complete |
|
t1.join() |
|
t2.join() |
|
print(f'The final counter is {counter}') |
在上面这个例子中,两个线程都试图同时修改counter
变量的值,counter
变量的值取决于哪个线程最后完成。
如果t1
线程在t2
线程之前完成,将看到以下输出:
counter=10 |
|
counter=20 |
|
The counter is 20 |
否则,看到以下输出:
counter=20 |
|
counter=10 |
|
The final counter is 10 |
使用 Lock 来防止条件竞争
为防止条件竞争,可以使用threading
模块中的Lock
类。锁有两种状态:锁定和解锁。
首先,创建一个Lock
类的实例:
<span style="background-color:#f8f8f8"><span style="color:#212529"><code class="language-python">lock = Lock()</code></span></span>
默认情况下,锁处于解锁状态,直到获得这个锁。
其次,通过调用acquire()
方法获取锁:
<span style="background-color:#f8f8f8"><span style="color:#212529"><code class="language-python">lock.acquire()</code></span></span>
第三,线程完成更改共享变量后释放锁:
<span style="background-color:#f8f8f8"><span style="color:#212529"><code class="language-python">lock.release()</code></span></span>
下面的例子展示了如何使用该Lock
对象来防止前面程序中的竞争条件:
from threading import Thread, Lock |
|
from time import sleep |
|
counter = 0 |
|
def increase(by, lock): |
|
global counter |
|
lock.acquire() |
|
local_counter = counter |
|
local_counter += by |
|
sleep(0.1) |
|
counter = local_counter |
|
print(f'counter={counter}') |
|
lock.release() |
|
lock = Lock() |
|
# create threads |
|
t1 = Thread(target=increase, args=(10, lock)) |
|
t2 = Thread(target=increase, args=(20, lock)) |
|
# start the threads |
|
t1.start() |
|
t2.start() |
|
# wait for the threads to complete |
|
t1.join() |
|
t2.join() |
|
print(f'The final counter is {counter}') |
还可以把上面的方法改成一个Counter
类。
from threading import Thread, Lock |
|
from time import sleep |
|
class Counter: |
|
def __init__(self): |
|
self.value = 0 |
|
self.lock = Lock() |
|
def increase(self, by): |
|
self.lock.acquire() |
|
current_value = self.value |
|
current_value += by |
|
sleep(0.1) |
|
self.value = current_value |
|
print(f'counter={self.value}') |
|
self.lock.release() |
|
counter = Counter() |
|
# create threads |
|
t1 = Thread(target=counter.increase, args=(10, )) |
|
t2 = Thread(target=counter.increase, args=(20, )) |
|
# start the threads |
|
t1.start() |
|
t2.start() |
|
# wait for the threads to complete |
|
t1.join() |
|
t2.join() |
|
print(f'The final counter is {counter.value}') |
总结
- 当两个线程同时访问共享变量时,就会出现条件竞争。
- 使用
Lock
对象来防止条件竞争。 - 调用
Lock
对象的acquire()
方法获取锁。 - 调用
Lock
对象的release()
方法释放之前获取的锁。