python多线程编程一
hkebao
|
1#
hkebao 发表于 2009-01-20 11:00
python多线程编程一
Python线程编程(一)线程对象
:感觉篇文章写得挺有意思的哦!转载一起分享 我们在做软件开发的时候很多要用到多线程技术。例如如果做一个下载软件象flashget就要用到、象在线视频工具realplayer也要用到因为要同时下载media stream还要播放。其实例子是很多的。 线程相对进程来说是“轻量级”的,操作系统用较少的资源创建和管理线程。程序中的线程在相同的内存空间中执行,并共享许多相同的资源。 在[color="#99cc00"]python中如何创建一个线程对象 如果你要创建一个线程对象,很简单,只要你的类继承[color="#99cc00"]threading.Thread,然后在[color="#99cc00"]__init__里首先调用[color="#99cc00"]threading.Thread的[color="#99cc00"]__init__方法即可 [color="#99cc00"]import threading class mythread(threading.Thread): def __init__(self, threadname): threading.Thread.__init__(self, name = threadname) .... 这才仅仅是个空线程,我可不是要他拉空车的,他可得给我干点实在活。很简单,重写类的[color="#99cc00"]run()方法即可,把你要在线程执行时做的事情都放到里面 [color="#99cc00"]import threading import time class mythread(threading.Thread): def __init__(...): .... def run(self): for i in range(10): print self.getName, i time.sleep(1) 以上代码我们让这个线程在执行之后每隔1秒输出一次信息到屏幕,10次后结束 [color="#99cc00"]getName()是[color="#99cc00"]threading.Thread类的一个方法,用来获得这个线程对象的[color="#99cc00"]name。还有一个方法[color="#99cc00"]setName()当然就是来设置这个线程对象的[color="#99cc00"]name的了。 如果要创建一个线程,首先就要先创建一个线程对象 [color="#99cc00"]mythread1 = mythread('mythread 1') 一个线程对象被创建后,他就处于“[color="#99cc00"]born”(诞生状态) 如何让这个线程对象开始运行呢?只要调用线程对象的[color="#99cc00"]start()方法即可 [color="#99cc00"]mythread1.start() 现在线程就处于“[color="#99cc00"]ready”状态或者也称为“[color="#99cc00"]runnable”状态。 奇怪吗?不是已经[color="#99cc00"]start了吗?为什么不称为“[color="#99cc00"]running”状态呢?其实是有原因的。因为我们的计算机一般是不具有真正并行处理能力的。我们所谓的多线程只是把时间分成片段,然后隔一个时间段就让一个线程执行一下,然后进入“[color="#99cc00"]sleeping ”状态,然后唤醒另一个在“[color="#99cc00"]sleeping”的线程,如此循环[color="#99cc00"]runnable->sleeping->runnable... ,只是因为计算机执行速度很快,而时间片段间隔很小,我们感受不到,以为是同时进行的。所以说一个线程在[color="#99cc00"]start了之后只是处在了可以运行的状态,他什么时候运行还是由系统来进行调度的。 那一个线程什么时候会“[color="#99cc00"]dead”呢?一般来说当线程对象的[color="#99cc00"]run方法执行结束或者在执行中抛出异常的话,那么这个线程就会结束了。系统会自动对“[color="#99cc00"]dead”状态线程进行清理。 如果一个线程[color="#99cc00"]t1在执行的过程中需要等待另一个线程[color="#99cc00"]t2执行结束后才能运行的话那就可以在[color="#99cc00"]t1在调用[color="#99cc00"]t2的[color="#99cc00"]join()方法 [color="#99cc00"].... def t1(...): ... t2.join() ... 这样[color="#99cc00"]t1在执行到[color="#99cc00"]t2.join()语句后就会等待[color="#99cc00"]t2结束后才会继续运行。 但是假如[color="#99cc00"]t1是个死循环的话那么等待就没有意义了,那怎么办呢?可以在调用[color="#99cc00"]t2的[color="#99cc00"]join()方法的时候给一个浮点数做超时参数,这样这个线程就不会等到花儿也谢了了。我等你10s,你不回来我还不允许我改嫁啊? [color="#99cc00"]def t1(...): ... t2.join(10) ... 如果一个进程的主线程运行完毕而子线程还在执行的话,那么进程就不会退出,直到所有子线程结束为止,如何让主线程结束的时候其他子线程也乖乖的跟老大撤退呢?那就要把那些不听话的人设置为听话的小弟,使用线程对象的[color="#99cc00"]setDaemon()方法,参数为[color="#99cc00"]bool型。[color="#99cc00"]True的话就代表你要听话,我老大(主线程)扯呼,你也要跟着撤,不能拖后腿。如果是[color="#99cc00"]False的话就不用那么听话了,老大允许你们将在外军命有所不受的。需要注意的是[color="#99cc00"]setDaemon()方法必须在线程对象没有调用[color="#99cc00"]start()方法之前调用,否则没效果。 [color="#99cc00"]t1 = mythread('t1') print t1.getName(),t1.isDaemon() t1.setDaemon(True) print t1.getName(),t1.isDaemon() t1.start() print 'main thread exit' 当执行到 [color="#99cc00"]print 'main thread exit' 后,主线程就退出了,当然[color="#99cc00"]t1这个线程也跟着结束了。但是如果不使用[color="#99cc00"]t1线程对象的[color="#99cc00"]setDaemon()方法的话,即便主线程结束了,还要等待t1线程自己结束才能退出进程。[color="#99cc00"]isDaemon()是用来获得一个线程对象的[color="#99cc00"]Daemonflag状态的。 如何来获得与线程有关的信息呢? 获得当前正在运行的线程的引用 [color="#99cc00"]running = threading.currentThread() 获得当前所有活动对象(即[color="#99cc00"]run方法开始但是未终止的任何线程)的一个列表 [color="#99cc00"]threadlist = threading.enumerate() 获得这个列表的长度 [color="#99cc00"]threadcount = threading.activeCount() 查看一个线程对象的状态调用这个线程对象的[color="#99cc00"]isAlive()方法,返回1代表处于“[color="#99cc00"]runnable”状态且没有“[color="#99cc00"]dead” [color="#99cc00"]threadflag = threading.isAlive() Python线程编程(二)简单的线程同步 多个执行线程经常要共享数据,如果仅仅读取共享数据还好,但是如果多个线程要修改共享数据的话就可能出现无法预料的结果。 假如两个线程对象[color="#ff6600"]t1和[color="#ff6600"]t2都要对数值[color="#ff6600"]num=0进行增1运算,那么[color="#ff6600"]t1和[color="#ff6600"]t2都各对[color="#ff6600"]num修改[color="#ff6600"]10次的话,那么[color="#ff6600"]num最终的结果应该为[color="#ff6600"]20。但是如果当[color="#ff6600"]t1取得[color="#ff6600"]num的值时(假如此时[color="#ff6600"]num为[color="#ff6600"]0),系统把[color="#ff6600"]t1调度为“[color="#ff6600"]sleeping”状态,而此时[color="#ff6600"]t2转换为“[color="#ff6600"]running”状态,此时[color="#ff6600"]t2获得的[color="#ff6600"]num的值也为[color="#ff6600"]0,然后他把[color="#ff6600"]num+1的值[color="#ff6600"]1赋给[color="#ff6600"]num。系统又把[color="#ff6600"]t2转化为“[color="#ff6600"]sleeping”状态,[color="#ff6600"]t1为“[color="#ff6600"]running”状态,由于[color="#ff6600"]t1已经得到[color="#ff6600"]num值为[color="#ff6600"]0,所以他也把[color="#ff6600"]num+1的值赋给了[color="#ff6600"]num为[color="#ff6600"]1。本来是[color="#ff6600"]2次增[color="#ff6600"]1运行,结果却是[color="#ff6600"]num只增了[color="#ff6600"]1次。类似这样的情况在多线程同时执行的时候是有可能发生的。所以为了防止这类情况的出现就要使用线程同步机制。 最简单的同步机制就是“锁” 锁对象用[color="#ff6600"]threading.RLock类创建 [color="#ff6600"]mylock = threading.RLock() 如何使用锁来同步线程呢?线程可以使用锁的[color="#ff6600"]acquire() (获得)方法,这样锁就进入“[color="#ff6600"]locked”状态。每次只有一个线程可以获得锁。如果当另一个线程试图获得这个锁的时候,就会被系统变为“[color="#ff6600"]blocked”状态,直到那个拥有锁的线程调用锁的[color="#ff6600"]release() (释放)方法,这样锁就会进入“[color="#ff6600"]unlocked”状态。“[color="#ff6600"]blocked”状态的线程就会收到一个通知,并有权利获得锁。如果多个线程处于“[color="#ff6600"]blocked”状态,所有线程都会先解除“[color="#ff6600"]blocked”状态,然后系统选择一个线程来获得锁,其他的线程继续沉默(“[color="#ff6600"]blocked”)。 [color="#ff6600"]import threading mylock = threading.RLock() class mythread(threading.Thread) ... def run(self ...): ... #此处 不可以 放置修改共享数据的代码 mylock.acquire() ... [color="#3366ff"]#此处 可以 放置修改共享数据的代码 mylock.release() ... [color="#3366ff"]#此处 不可以 放置修改共享数据的代码 我们把修改共享数据的代码称为“临界区”,必须将所有“临界区”都封闭在同一锁对象的[color="#ff6600"]acquire()和[color="#ff6600"]release()方法调用之间。 锁只能提供最基本的同步级别。有时需要更复杂的线程同步,例如只在发生某些事件时才访问一个临界区(例如当某个数值改变时)。这就要使用“条件变量”。 条件变量用[color="#ff6600"]threading.Condition类创建 [color="#ff6600"]mycondition = threading.Condition() 条件变量是如何工作的呢?首先一个线程成功获得一个条件变量后,调用此条件变量的[color="#ff6600"]wait()方法会导致这个线程释放这个锁,并进入“[color="#ff6600"]blocked”状态,直到另一个线程调用同一个条件变量的[color="#ff6600"]notify()方法来唤醒那个进入“[color="#ff6600"]blocked”状态的线程。如果调用这个条件变量的[color="#ff6600"]notifyAll()方法的话就会唤醒所有的在等待的线程。 如果程序或者线程永远处于“[color="#ff6600"]blocked”状态的话,就会发生死锁。所以如果使用了锁、条件变量等同步机制的话,一定要注意仔细检查,防止死锁情况的发生。对于可能产生异常的临界区要使用异常处理机制中的[color="#ff6600"]finally子句来保证释放锁。等待一个条件变量的线程必须用[color="#ff6600"]notify()方法显式的唤醒,否则就永远沉默。保证每一个[color="#ff6600"]wait()方法调用都有一个相对应的[color="#ff6600"]notify()调用,当然也可以调用[color="#ff6600"]notifyAll()方法以防万一。 Python线程编程(三)同步队列 我们经常会采用生产者/消费者关系的两个线程来处理一个共享缓冲区的数据。例如一个生产者线程接受用户数 据放入一个共享缓冲区里,等待一个消费者线程对数据取出处理。但是如果缓冲区的太小而生产者和消费者两个异步线程的速度不同时,容易出现一个线程等待另一 个情况。为了尽可能的缩短共享资源并以相同速度工作的各线程的等待时间,我们可以使用一个“队列”来提供额外的缓冲区。创建一个“队列”对象 [color="#ff9900"]import Queue myqueue = Queue.Queue(maxsize = 10) [color="#ff9900"]Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过[color="#ff9900"]Queue的构造函数的可选参数[color="#ff9900"]maxsize来设定队列长度。如果[color="#ff9900"]maxsize小于[color="#ff9900"]1就表示队列长度无限。 将一个值放入队列中 [color="#ff9900"]myqueue.put(10) 调用队列对象的[color="#ff9900"]put()方法在队尾插入一个项目。[color="#ff9900"]put()有两个参数,第一个[color="#ff9900"]item为必需的,为插入项目的值;第二个[color="#ff9900"]block为可选参数,默认为[color="#ff9900"]1。如果队列当前为空且[color="#ff9900"]block为[color="#ff9900"]1,[color="#ff9900"]put()方法就使调用线程暂停,直到空出一个数据单元。如果[color="#ff9900"]block为[color="#ff9900"]0,[color="#ff9900"]put方法将引发[color="#ff9900"]Full异常。 将一个值从队列中取出 [color="#ff9900"]myqueue.get() 调用队列对象的[color="#ff9900"]get()方法从队头删除并返回一个项目。可选参数为[color="#ff9900"]block,默认为[color="#ff9900"]1。如果队列为空且[color="#ff9900"]block为[color="#ff9900"]1,[color="#ff9900"]get()就使调用线程暂停,直至有项目可用。如果[color="#ff9900"]block为0,队列将引发[color="#ff9900"]Empty异常。 我们用一个例子来展示如何使用[color="#ff9900"]Queue [color="#ff9900"]# queue_example.py from Queue import Queue import threading import random import time [color="#ff9900"]# Producer thread class Producer(threading.Thread): def __init__(self, threadname, queue): threading.Thread.__init__(self, name = threadname) self.sharedata = queue def run(self): for i in range(20): print self.getName(),'adding',i,'to queue' self.sharedata.put(i) time.sleep(random.randrange(10)/10.0) print self.getName(),'Finished' [color="#ff9900"]# Consumer thread class Consumer(threading.Thread): def __init__(self, threadname, queue): threading.Thread.__init__(self, name = threadname) self.sharedata = queue def run(self): for i in range(20): print self.getName(),'got a value:',self.sharedata.get() time.sleep(random.randrange(10)/10.0) print self.getName(),'Finished' [color="#ff9900"]# Main thread def main(): queue = Queue() producer = Producer('Producer', queue) consumer = Consumer('Consumer', queue) [color="#ff9900"]print 'Starting threads ...' producer.start() consumer.start() [color="#ff9900"]producer.join() consumer.join() [color="#ff9900"]print 'All threads have terminated.' [color="#ff9900"]if __name__ == '__main__': main() 示例代码中实现了两个类:生产者类[color="#ff9900"]Producer和消费者类[color="#ff9900"]Consumer。前者在一个随机的时间内放入一个值到队列[color="#ff9900"]queue中然后显示出来,后者在一定随机的时间内从队列[color="#ff9900"]queue中取出一个值并显示出来。 |