python——线程与多线程进阶
之前我们已经学会如何在代码块中创建新的线程去执行我们要同步执行的多个任务,但是线程的世界远不止如此。接下来,我们要介绍的是整个threading模块。threading基于Java的线程模型设计。锁(Lock)和条件变量(Condition)在Java中是对象的基本行为(每一个对象都自带了锁和条件变量),而在Python中则是独立的对象,所以python的threading模块中还提供了Lock,Rlock,Condition,Event等常用类,它们在python中是独立于Tread模块的,但是却与线程紧密相关,不可分割。
需要注意的是:python的线程中没有优先级、线程组,也不能被停止、暂停、恢复、中断,线程只能随着线程中的代码执行完毕而被销毁。查了n多资料之后终于接受了以上事实,个人觉得这是python的一个坑,导致了我在实现线程池的时候无法停止已经注入了方法且执行超时的线程。
threading模块提供的类:
Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, local.
threading 模块提供的常用方法:
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
threading模块常用类详解
Thread类:我们使用Thread类来创建新的线程
- start 线程准备就绪,等待CPU调度
- setName 为线程设置名称
- getName 获取线程名称
- setDaemon 设置为后台线程或前台线程(默认)
如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止 - join 逐个执行每个线程,执行完毕后继续往下执行,该方法是有高级用法的,代码在下面
- run 线程被cpu调度后执行Thread类对象的run方法
1 import time 2 import threading 3 4 def printNum(a): 5 print 'num:',a 6 time.sleep(1) 7 8 def ThreadTest(i): 9 return threading.Thread(target=printNum, args=(999,)) 10 11 thread_arr = [] 12 for i in range(10): 13 t = ThreadTest(i) 14 thread_arr.append(t) 15 16 for t in thread_arr: 17 t.start() 18 19 for t in thread_arr: 20 t.join() 21 22 print 'finished'
Lock类和Rlock类:由于线程之间随机调度:某线程可能在执行n条后,CPU接着执行其他线程。为了多个线程同时操作一个内存中的资源时不产生混乱,我们使用锁
- acquire 给线程上锁
- release 给线程解锁
无论是lock还是rlock,提供的方法都非常简单,acquire和release。但是rlock和lock的区别是什么呢?RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况。注意:如果使用RLock,那么acquire和release必须成对出现,即调用了n次acquire,必须调用n次的release才能真正释放所占用的锁。
1 #!/usr/bin/env python 2 #-*-coding:utf-8-*- 3 __author__ = 'Eva_J' 4 5 import threading 6 lock = threading.Lock() #Lock对象 7 lock.acquire() 8 lock.acquire() #产生了死锁。 9 lock.release() 10 lock.release() 11 12 13 import threading 14 rLock = threading.RLock() #RLock对象 15 rLock.acquire() 16 rLock.acquire() #在同一线程内,程序不会堵塞。 17 rLock.release() 18 rLock.release()
Condition类:条件变量对象能让一个线程停下来,等待其它线程满足了某个“条件”。如,状态的改变或值的改变。
- acquire 给线程上锁
- wait wait方法释放当前线程占用的锁,同时挂起线程,直至被唤醒或超时(需timeout参数)。当线程被唤醒并重新占有锁的时候,程序才会继续执行下去。
- notify 唤醒一个挂起的线程(如果存在挂起的线程)。注:notify()方法不会释放所占用的锁。
- notifyall 调用这个方法将通知等待池中所有线程,这些线程都将进入锁定池尝试获得锁定。此方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。
比较经典的例子是下面这个生产者与消费者的例子,这个例子网上一搜到处都是,这里简单解释一下这段代码的意义,代码中写了两个类,Consumer和Producer,分别继承了Thread类,我们分别初始化这两个类获得了c和p对象,并启动这两个线程。则这两个线程去执行run方法(这里与Thread类内部的调度有关),定义了producer全局变量和condition对象为全局变量,当producer不大于1时,消费者线程被condition对象阻塞,不能继续消费(这里是不再递减),当producer不小于10时,生产者线程被condition对象阻塞,不再生产(这里是不再累加),代码在下面,拿去执行,断点一下就明白了。
1 #!/usr/bin/env python 2 #-*-coding:utf-8-*- 3 __author__ = 'Eva_J' 4 5 import threading 6 import time 7 8 condition = threading.Condition() 9 products = 0 10 11 class Producer(threading.Thread): 12 def __init__(self): 13 threading.Thread.__init__(self) 14 15 def run(self): 16 global condition, products 17 while True: 18 if condition.acquire(): 19 if products < 10: 20 products += 1; 21 print "Producer(%s):deliver one, now products:%s" %(self.name, products) 22 condition.notify() 23 else: 24 print "Producer(%s):already 10, stop deliver, now products:%s" %(self.name, products) 25 condition.wait(); 26 condition.release() 27 time.sleep(2) 28 29 class Consumer(threading.Thread): 30 def __init__(self): 31 threading.Thread.__init__(self) 32 33 def run(self): 34 global condition, products 35 while True: 36 if condition.acquire(): 37 if products > 1: 38 products -= 1 39 print "Consumer(%s):consume one, now products:%s" %(self.name, products) 40 condition.notify() 41 else: 42 print "Consumer(%s):only 1, stop consume, products:%s" %(self.name, products) 43 condition.wait(); 44 condition.release() 45 time.sleep(2) 46 47 if __name__ == "__main__": 48 for p in range(0, 2): 49 p = Producer() 50 p.start() 51 52 for c in range(0, 10): 53 c = Consumer() 54 c.start()
Event类:通用的条件变量。多个线程可以等待某个事件的发生,在事件发生后,所有的线程都会被激活。
- event.wait(timeout) 当Flag为‘False’时,线程将被阻塞
- clear 将“Flag”设置为False
- set 将“Flag”设置为True
- is_set 返回当前‘Flag’
这是一个比较关键的类,我在写线程池的时候看到python的threadpool模块也用到了。它的意义在于可以控制属于同一个线程类的多个实例化对象,让他们同时阻塞或者执行。配合队列来实现一个线程池非常好用。在接下里的博客中我们还要继续介绍。先放一个小例子在这里练练手,了解一下event的用法。
1 #!/usr/bin/env python 2 #-*-coding:utf-8-*- 3 __author__ = 'Eva_J' 4 5 import threading 6 import time 7 8 event = threading.Event() 9 10 def func(): 11 # 等待事件,进入等待阻塞状态 12 print '%s wait for event...' % threading.currentThread().getName() 13 event.wait() 14 15 # 收到事件后进入运行状态 16 print '%s recv event.' % threading.currentThread().getName() 17 18 t1 = threading.Thread(target=func) 19 t2 = threading.Thread(target=func) 20 t1.start() 21 t2.start() 22 23 time.sleep(2) 24 25 # 发送事件通知 26 print 'MainThread set event.' 27 event.set()
参考文献:
python多线程学习小结:http://www.myexception.cn/perl-python/1688021.html
python线程指南:http://www.cnblogs.com/huxi/archive/2010/06/26/1765808.html
threading.RLock和threading.Lock:http://blog.sina.com.cn/s/blog_5dd2af0901012rad.html
python的进程、线程与协程:http://www.cnblogs.com/wupeiqi/articles/5040827.html