详解Python中的多线程编程
一、简介 多线程编程技术可以实现代码并行性,优化处理能力,同时功能的更小划分可以使代码的可重用性更好。Python中threading和Queue模块可以用来实现多线程编程。 #!/usr/bin/env python import thread from time import sleep,ctime def loop0(): print '+++start loop 0 at:',ctime() sleep(4) print '+++loop 0 done at:',ctime() def loop1(): print '***start loop 1 at:',ctime() sleep(2) print '***loop 1 done at:',ctime() def main(): print '------starting at:',ctime() thread.start_new_thread(loop0,()) thread.start_new_thread(loop1,()) sleep(6) print '------all DONE at:',ctime() if __name__ == '__main__': main() thread 模块提供的简单的多线程的机制,两个循环并发地被执行,总的运行时间为最慢的那个线程的运行时间(主线程6s),而不是所有的线程的运行时间之和。start_new_thread()要求要有前两个参数,就算想要运行的函数不要参数,也要传一个空的元组。 sleep(6)是让主线程停下来,主线程一旦运行结束,就关闭运行着其他两个线程。但这可能造成主线程过早或过晚退出,那就要使用线程锁,可以在两个子线程都退出后,主线程立即退出。 #!/usr/bin/env python import thread from time import sleep,ctime loops = [4,2] def loop(nloop,nsec,lock): print '+++start loop:',nloop,'at:',ctime() sleep(nsec) print '+++loop:','done at:',ctime() lock.release() def main(): print '---starting threads...' locks = [] nloops = range(len(loops)) for i in nloops: lock = thread.allocate_lock() lock.acquire() locks.append(lock) for i in nloops: thread.start_new_thread(loop,(i,loops[i],locks[i])) for i in nloops: while locks[i].locked(): pass print '---all DONE at:',ctime() if __name__ == '__main__': main() 4、threading模块 thread模块不支持守护线程,当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。而threading模块支持守护线程,守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求它就在那等着,如果设定一个线程为守护线程,就表示这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。如果主线程退出不用等待那些子线程完成,那就设定这些线程的daemon属性,即在线程thread.start()开始前,调用setDaemon()函数设定线程的daemon标志(thread.setDaemon(True))就表示这个线程“不重要”。如果想要等待子线程完成再退出,那就什么都不用做或者显式地调用thread.setDaemon(False)以保证其daemon标志为False,可以调用thread.isDaemon()函数来判断其daemon标志的值。新的子线程会继承其父线程的daemon标志,整个Python会在所有的非守护线程退出后才会结束,即进程中没有非守护线程存在的时候才结束。 创建一个Thread的实例,传给它一个函数 #!/usr/bin/env python import threading from time import sleep,ctime loops = [ 4,2 ] def loop(nloop,nsec): print '+++start loop:',ctime() def main(): print '---starting at:',ctime() threads = [] nloops = range(len(loops)) for i in nloops: t = threading.Thread(target=loop,args=(i,loops[i])) threads.append(t) for i in nloops: # start threads threads[i].start() for i in nloops: # wait for all threads[i].join() # threads to finish print '---all DONE at:',ctime() if __name__ == '__main__': main() 实例化一个Thread(调用 Thread())与调用thread.start_new_thread()之间最大的区别就是,新的线程不会立即开始。在创建线程对象,但不想马上开始运行线程的时候,这是一个很有用的同步特性。所有的线程都创建了之后,再一起调用 start()函数启动,而不是创建一个启动一个。而且也不用再管理一堆锁(分配锁、获得锁、释放锁、检查锁的状态等),只要简单地对每个线程调用join()主线程等待子线程的结束即可。join()还可以设置timeout的参数,即主线程等到超时为止。 #!/usr/bin/env python import threading from time import sleep,2 ] class ThreadFunc(object): def __init__(self,func,args,name=''): self.name = name self.func = func self.args = args def __call__(self): apply(self.func,self.args) def loop(nloop,nsec): print 'start loop',ctime() sleep(nsec) print 'loop',ctime() def main(): print 'starting at:',ctime() threads = [] nloops = range(len(loops)) for i in nloops: # create all threads t = threading.Thread(target=ThreadFunc(loop,loops[i]),loop.__name__)) threads.append(t) for i in nloops: # start all threads threads[i].start() for i in nloops: # wait for completion threads[i].join() print 'all DONE at:',ctime() if __name__ == '__main__': main() 与传一个函数很相似的另一个方法是在创建线程的时候,传一个可调用的类的实例供线程启动的时候执行,这是多线程编程的一个更为面向对象的方法。相对于一个或几个函数来说,类对象里可以使用类的强大的功能。创建新线程的时候,Thread对象会调用ThreadFunc对象,这时会用到一个特殊函数__call__()。由于已经有了要用的参数,所以就不用再传到Thread()的构造函数中。由于有一个参数的元组,这时要使用apply()函数或使用self.res = self.func(*self.args)。 #!/usr/bin/env python import threading from time import sleep,2 ] class MyThread(threading.Thread): def __init__(self,name=''): threading.Thread.__init__(self) self.name = name self.func = func self.args = args def getResult(self): return self.res def run(self): print 'starting',self.name,ctime() self.res = apply(self.func,self.args) print self.name,'finished at:',ctime() def loop(nloop,ctime() threads = [] nloops = range(len(loops)) for i in nloops: t = MyThread(loop,loop.__name__) threads.append(t) for i in nloops: threads[i].start() for i in nloops: threads[i].join() print 'all DONE at:',ctime() if __name__ == '__main__': main()
子类化Thread类,MyThread子类的构造函数一定要先调用基类的构造函数,特殊函数__call__()在子类中,名字要改为run()。在 MyThread类中,加入一些用于调试的输出信息,把代码保存到myThread模块中,并导入这个类。除使用apply()函数来运行这些函数之外,还可以把结果保存到实现的self.res属性中,并创建一个新的函数getResult()来得到结果。 5、Queue模块 Queue模块可以用来进行线程间通讯,让各个线程之间共享数据。Queue解决生产者-消费者的问题,现在创建一个队列,让生产者线程把新生产的货物放进去供消费者线程使用。生产者生产货物所要花费的时间无法预先确定,消费者消耗生产者生产的货物的时间也是不确定的。 #!/usr/bin/env python from random import randint from time import sleep from Queue import Queue from myThread import MyThread def writeQ(queue): print '+++producing object for Q...',queue.put('xxx',1) print "+++size now:",queue.qsize() def readQ(queue): val = queue.get(1) print '---consumed object from Q... size now', queue.qsize() def writer(queue,loops): for i in range(loops): writeQ(queue) sleep(randint(1,3)) def reader(queue,loops): for i in range(loops): readQ(queue) sleep(randint(2,5)) funcs = [writer,reader] nfuncs = range(len(funcs)) def main(): nloops = randint(2,5) q = Queue(32) threads = [] for i in nfuncs: t = MyThread(funcs[i],(q,nloops), funcs[i].__name__) threads.append(t) for i in nfuncs: threads[i].start() for i in nfuncs: threads[i].join() print '***all DONE' if __name__ == '__main__': main() 这个实现中使用了Queue对象和随机地生产(和消耗)货物的方式。生产者和消费者相互独立并且并发地运行,它们不一定是轮流执行的(随机数模拟)。writeQ()和readQ()函数分别用来把对象放入队列和消耗队列中的一个对象,在这里使用字符串'xxx'来表示队列中的对象。writer()函数就是一次往队列中放入一个对象,等待一会然后再做同样的事,一共做指定的次数,这个次数是由脚本运行时随机生成的。reader()函数做的事比较类似,只是它是用来消耗对象的。 三、总结 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |