Python 多线程threading学习
上节我们学习了threading.Thread和threading.Timer,这次我们继续来学习threading的接下来的内容:threading.Lock、threading.RLock、threading.Condition
我们先来学习下Lock的内容:
对于下面这个程序的执行,明显是有问题的,尤其是在多线程执行过程中
#encoding=gbk
import threading
import time
counter=0
class add_thread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global counter
time.sleep(1)
counter += 1
print "%s,set counter:%s" %(self.name,counter)
if __name__ == "__main__":
for i in range(200):
thread = add_thread()
thread.start()
多执行几次,就会出现问题,如下面这个:
Thread-167,set counter:167Thread-169,set counter:168
Thread-168,set counter:169
这里看以看出来,全局资源counter被线程抢占的情况,问题 产生的原因就是没有控制多线程访问同一个资源,让线程的执行结果不可预料,这就是线程不安全的。这个方法的避免就是互斥锁了。
下面我们看下threading.Lock类:
A primitive lock is a synchronization primitive that is not owned by a particular thread when locked. In Python, it is currently the lowest level synchronization primitive available, implemented directly by the thread extension module.
按照描述,这个Lock是Thread的最低级别的同步实现,下面还有一段话:
A primitive lock is in one of two states, “locked” or “unlocked”. It is created in the unlocked state. It has two basic methods, acquire() and release(). When the state is unlocked, acquire() changes the state to locked and returns immediately. When the state is locked, acquire() blocks until a call to release() in another thread changes it to unlocked, then the acquire() call resets it to locked and returns. The release() method should only be called in the locked state; it changes the state to unlocked and returns immediately. If an attempt is made to release an unlocked lock, a RuntimeError will be raised.
简单的说就是同一时刻只能有一把锁,并且锁的状态互斥。锁上加锁等待,无锁解锁异常,无锁加锁和锁上解锁都是正常的。
加锁后的代码:
#encoding=gbk
import threading
import time
counter=0
mutex=threading.Lock()
class add_thread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global counter,mutex
time.sleep(1)
if mutex.acquire():
counter += 1
print "%s,set counter:%s" %(self.name,counter)
mutex.release()
if __name__ == "__main__":
for i in range(200):
thread = add_thread()
thread.start()
多运行几次,运行结果都是正常的。
现在有个额外的考虑:
有两个线程互相等待对方释放资源,这种情况下死锁怎么办?
如果线程持有对象锁,又需要获取锁,这种锁嵌套怎么办?
第一个问题需要正确有序的分配资源,有名的算法是银行家算法,这个不是我们本节的重点。后者这种重复获取情况,python给我们提供了一种可重入锁,threading.RLock的官方声明如下:
A reentrant lock is a synchronization primitive that may be acquired multiple times by the same thread. Internally, it uses the concepts of “owning thread” and “recursion level” in addition to the locked/unlocked state used by primitive locks. In the locked state, some thread owns the lock; in the unlocked state, no thread owns it.
这个Rlock的重点是acquire方法和release方法必须成对出现,否则就会出问题。
上面的Lock和RLock能够解决一部分线程对于公共资源的访问问题,不过线程编程模型不可能这么简单,如果出现资源出现动态变化的情况,线程需要根据资源的状态进行操作,这个又该怎么做?Python同样提供了对于复杂线程问题的同步支持,threading.Condiion
A condition variable is always associated with some kind of lock; this can be passed in or one will be created by default. (Passing one in is useful when several condition variables must share the same lock.)
Condition成为条件变量,除了提供acquire方法和release方法外,还提供了notify方法、wait方法和notifyAll方法。这个可以使Condition根据条件进行判断。如果不满足条件wait,在满足条件时执行动作,执行完成后notify其它线程。重复这个过程,解决复杂的同步过程。其中最为典型的就是生产者-消费者模型。下面我们用Condition同步实现这个模型。
#encoding=gbk
import threading
import time
condition = threading.Condition()
products = 0
class Producer(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global condition, products
while True:
if condition.acquire():
if products < 10:
products += 1;
print "Producer(%s):deliver one, now products:%s" %(self.name, products)
condition.notify()
else:
print "Producer(%s):already 10, now products:%s" %(self.name, products)
condition.wait();
condition.release()
time.sleep(2)
class Consumer(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global condition, products
while True:
if condition.acquire():
if products > 1:
products -= 1
print "Consumer(%s):consume one, products:%s" %(self.name, products)
condition.notify()
else:
print "Consumer(%s):only 1, stop consume" %(self.name)
condition.wait();
condition.release()
time.sleep(2)
if __name__ == "__main__":
for p in range(0, 2):
p = Producer()
p.start()
for c in range(0, 10):
c = Consumer()
c.start()
看下执行结果,重点是分析condition在Producer-Consumer模型中的作用。
下节我们继续学习threading的其它内容