多线程threading模块

python的多线程编程

简介

多线程编程技术能够实现代码并行性,优化处理能力,同时功能的更小划分可使代码的可重用性更好。Python中threading和Queue模块能够用来实现多线程编程。python

详解

线程和进程

进程(有时被称为重量级进程)是程序的一次执行。每一个进程都有本身的地址空间、内存、数据栈以及其它记录其运行轨迹的辅助数据。操做系统管理在其上运行的全部进程,并为这些进程公平地分配时间。进程也能够经过fork和spawn操做来完成其它的任务,不过各个进程有本身的内存空间、数据栈等,因此只能使用进程间通信(IPC),而不能直接共享信息。
线程(有时被称为轻量级进程)跟进程有些类似,不一样的是全部的线程运行在同一个进程中,共享相同的运行环境。它们能够想像成是在主进程或“主线程”中并行运行的“迷你进程”。线程有开始、顺序执行和结束三部分,它有一个本身的指令指针,记录本身运行到什么地方。线程的运行可能被抢占(中断)或暂时的被挂起(也叫睡眠)让其它的线程运行,这叫作让步。一个进程中的各个线程之间共享同一片数据空间,因此线程之间能够比进程之间更方便地共享数据以及相互通信。线程通常都是并发执行的,正是因为这种并行和数据共享的机制使得多个任务的合做变为可能。实际上,在单CPU的系统中,真正的并发是不可能的,每一个线程会被安排成每次只运行一小会,而后就把CPU让出来,让其它的线程去运行。在进程的整个运行过程当中,每一个线程都只作本身的事,在须要的时候跟其它的线程共享运行的结果。多个线程共同访问同一片数据不是彻底没有危险的,因为数据访问的顺序不同,有可能致使数据结果的不一致的问题,这叫作竞态条件。而大多数线程库都带有一系列的同步原语,来控制线程的执行和数据的访问。程序员

使用线程

(1)全局解释器锁(GIL)
Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中能够“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。在多线程环境中,Python 虚拟机按如下方式执行:a、设置 GIL;b、切换到一个线程去运行;c、运行指定数量的字节码指令或者线程主动让出控制(能够调用 time.sleep(0));d、把线程设置为睡眠状态;e、解锁 GIL;d、再次重复以上全部步骤。
在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(因为在这期间没有Python的字节码被运行,因此不会作线程切换)编写扩展的程序员能够主动解锁GIL。
(2)退出线程
当一个线程结束计算,它就退出了。线程能够调用thread.exit()之类的退出函数,也可使用Python退出进程的标准方法,如sys.exit()或抛出一个SystemExit异常等。不过,不能够直接“杀掉”("kill")一个线程。
不建议使用thread模块,很明显的一个缘由是,当主线程退出的时候,全部其它线程没有被清除就退出了。另外一个模块threading就能确保全部“重要的”子线程都退出后,进程才会结束。
(3)Python的线程模块
Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。thread和threading模块容许程序员建立和管理线程。thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块容许用户建立一个能够用于多个线程之间共享数据的队列数据结构。
避免使用thread模块,由于更高级别的threading模块更为先进,对线程的支持更为完善,并且使用thread模块里的属性有可能会与threading出现冲突;其次低级别的thread模块的同步原语不多(实际上只有一个),而threading模块则有不少;再者,thread模块中当主线程结束时,全部的线程都会被强制结束掉,没有警告也不会有正常的清除工做,至少threading模块能确保重要的子线程退出后进程才退出。编程

threading模块

thread模块不支持守护线程,当主线程退出时,全部的子线程不论它们是否还在工做,都会被强行退出。而threading模块支持守护线程,守护线程通常是一个等待客户请求的服务器,若是没有客户提出请求它就在那等着,若是设定一个线程为守护线程,就表示这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。若是主线程退出不用等待那些子线程完成,那就设定这些线程的daemon属性,即在线程thread.start()开始前,调用setDaemon()函数设定线程的daemon标志(thread.setDaemon(True))就表示这个线程“不重要”。若是想要等待子线程完成再退出,那就什么都不用作或者显式地调用thread.setDaemon(False)以保证其daemon标志为False,能够调用thread.isDaemon()函数来判断其daemon标志的值。新的子线程会继承其父线程的daemon标志,整个Python会在全部的非守护线程退出后才会结束,即进程中没有非守护线程存在的时候才结束。
1)threading的Thread类
它有不少thread模块里没有的函数,Thread对象的函数:服务器

201549112739440.png

建立一个Thread的实例,传给它一个函数

#!/usr/bin/env python
import threading
from time import sleep,ctime
loops=[4,2]
def loop(nloop,nsec):
        print 'start loop',nloop,'at:',ctime()
        sleep(nsec)
        print 'loop',nloop,'done at:',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:
                threads[i].start()
        for i in nloops:
                threads[i].join()
        print 'all done at',ctime()
if __name__=='__main__':
        main()

运行结果:数据结构

root@hanfeifei-HP-ProDesk-680-G2-MT:/mnt/han# python threading1.py 
***starting at: Sun Jul 31 17:37:39 2016
start loop 0 at: Sun Jul 31 17:37:39 2016
start loop 1 at: Sun Jul 31 17:37:39 2016
loop 1 done at: Sun Jul 31 17:37:41 2016
loop 0 done at: Sun Jul 31 17:37:43 2016
all done at Sun Jul 31 17:37:43 2016

实例化一个Thread(调用 Thread())与调用thread.start_new_thread()之间最大的区别就是,新的线程不会当即开始。在建立线程对象,但不想立刻开始运行线程的时候,这是一个颇有用的同步特性。全部的线程都建立了以后,再一块儿调用 start()函数启动,而不是建立一个启动一个。并且也不用再管理一堆锁(分配锁、得到锁、释放锁、检查锁的状态等),只要简单地对每一个线程调用join()主线程等待子线程的结束便可。join()还能够设置timeout的参数,即主线程等到超时为止。
join()的另外一个比较重要的方面是它能够彻底不用调用,一旦线程启动后,就会一直运行,直到线程的函数结束,退出为止。若是主线程除了等线程结束外,还有其它的事情要作,那就不用调用 join(),只有在等待线程结束的时候才调用join()。多线程

建立一个Thread的实例,传给它一个可调用的类对象

#!/usr/bin/env python
import threading
from time import sleep, ctime
loops=[4,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',nloop,'at:',ctime()
    sleep(nsec)
    print 'loop',nloop,'done at:',ctime()
def main():
    print 'starting at:',ctime()
    threads=[]
    nloops=range(len(loops))
    for i in nloops:
        t=threading.Thread(target=ThreadFunc(loop,(i,loops[i]),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对象会调用ThreadFunc对象,这时会用到一个特殊函数__call__()。因为已经有了要用的参数,因此就不用再传到Thread()的构造函数中。因为有一个参数的元组,这时要使用apply()函数或使用self.res = self.func(*self.args)。

从Thread派生出一个子类,建立一个这个子类的实例并发

#!/usr/bin/env python 
  
import threading 
from time import sleep, ctime 
  
loops = [ 4, 2 ] 
  
class MyThread(threading.Thread): 
  def __init__(self, func, args, 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, 'at:', ctime() 
    self.res = apply(self.func, self.args) 
    print self.name, 'finished at:', ctime() 
  
def loop(nloop, nsec): 
  print 'start loop', nloop, 'at:', ctime() 
  sleep(nsec) 
  print 'loop', nloop, 'done at:', ctime() 
  
def main(): 
  print 'starting at:', ctime() 
  threads = [] 
  nloops = range(len(loops)) 
  
  for i in nloops: 
    t = MyThread(loop, (i, loops[i]), 
    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()来获得结果。app

Queue模块

Queue模块能够用来进行线程间通信,让各个线程之间共享数据。Queue解决生产者-消费者的问题,如今建立一个队列,让生产者线程把新生产的货物放进去供消费者线程使用。生产者生产货物所要花费的时间没法预先肯定,消费者消耗生产者生产的货物的时间也是不肯定的。dom

#!/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()

~函数

相关文章
相关标签/搜索