python多线程模块threading中,有一个Condition对象。这个对象可以用来控制更加复杂的线程间的同步。
Condition本身包含一个Lock或者Rlock,可以在创建Congdition对象的时候传一个进去,如果不传,默认创建一个Rlock。通过with语句,即可acquire,并且自动release。不过这不是Condition对象的用法,它的亮点在于wait和notify。
在通过with语句获取lock之后,线程可以通过wait,release这个lock,并且block。或者线程可以通过notify,让渡(relinquish)自己的lock给在这个condition对象上wait的线程用。
Note: the notify() and notify_all() methods don’t release the lock; this means that the thread or threads awakened will not return from their wait() call immediately, but only when the thread that called notify() or notify_all() finally relinquishes ownership of the lock.
notify之后,wait在那个condition上的thread,不会离开从wait返回,而是等待调用notify的thread执行完它的代码逻辑,relinquish ownership of the lock。即明确指示wait在那个condition上的thread执行(用notify_all,就需要在一个范围内随机调度)。好性感的操控!
from time import sleep
from threading import Thread, Lock, Condition
mutex = Lock()
conv = Condition(mutex)
def target1():
with conv:
print(1)
sleep(2)
conv.notify()
conv.wait()
print(3)
sleep(2)
conv.notify()
conv.wait()
print(5)
sleep(2)
conv.notify()
conv.wait()
print(7)
conv.notify()
def target2():
with conv:
print(2)
sleep(2)
conv.notify()
conv.wait()
print(4)
sleep(2)
conv.notify()
conv.wait()
print(6)
sleep(2)
conv.notify()
conv.wait()
print(8)
thread_01 = Thread(target=target1, args=())
thread_02 = Thread(target=target2, args=())
thread_01.start()
thread_02.start()
这段代码执行的时候,会连续打印1-8,这就是两个线程间同步的更加精准的控制手段。两个线程都是在wait的时候释放lock,此时notify已经发出,那个wait的线程继续执行。
学习到这里,我有个疑问,如果两个线程这么来来回回的传递lock,第3个线程会不会饿死?于是,我写了下面的测试代码。
from time import sleep
from threading import Thread, Lock, Condition
mutex = Lock()
conv = Condition(mutex)
def target1():
with conv:
print(1)
sleep(2)
conv.notify()
conv.wait()
print(3)
sleep(2)
conv.notify()
conv.wait()
print(5)
sleep(2)
conv.notify()
conv.wait()
print(7)
conv.notify()
def target2():
with conv:
print(2)
sleep(2)
conv.notify()
conv.wait()
print(4)
sleep(2)
conv.notify()
conv.wait()
print(6)
sleep(2)
conv.notify()
conv.wait()
print(8)
def target3():
for i in range(4):
with mutex:
print('what...')
sleep(2)
thread_01 = Thread(target=target1, args=())
thread_02 = Thread(target=target2, args=())
thread_03 = Thread(target=target3, args=())
thread_01.start()
thread_02.start()
thread_03.start()
看来,线程饿死的情况是不会发生的,python线程间的调度策略,依然会保证上面代码中target3的调度。从测试情况看,线程target3的调度有随机性,但是target1和target2相互间的执行顺序是严格控制的。
不过,个人感觉使用Condition对象操作线程间的执行,还是很容易出现dead wait的情况。多线程代码还是需要精心调试的,比如上面的测试代码,如果把3个线程的start顺序交换一下,就会出现dead wait:
thread_02.start()
thread_01.start()
thread_03.start()
先打印2,然后1......,最后打印出8后,代码没有notify,target1线程就出现了dead wait。
最后,with conv这行代码,其实效果跟wait很相似,某一个线程wait后,另一个线程才能执行完with conv这行代码,进入内部block执行。
在多线程编程时,越是强大的东西,代码越要小心...
-- EOF --