zoukankan      html  css  js  c++  java
  • python 多线程编程之threading模块(Thread类)创建线程的三种方法

    摘录 python核心编程

    上节介绍的thread模块,是不支持守护线程的。当主线程退出的时候,所有的子线程都将终止,不管他们是否仍在工作。

    本节开始,我们开始介绍python的另外多线程模块threading,该模块支持守护线程,其工作方式:守护线程一般是一个等待客户端请求的服务器。如果没有客户端请求,守护线程就是空闲的。如果把一个线程设置为守护线程,就表示这个线程是不重要的,进程退出时不需要等待这个线程执行完成。

    如果主线程准备退出的时候,不需要等待某些子线程完成,就可以为这些子线程设置守护线程标记。该标记值为真的时候,标示线程是不重要的,或者说该线程只是用来等待客户端请求而不做其他任何事情。

    使用下面的语句:thread.daemon=True 可以将一个线程设置为守护线程。同样的也可以通过这个值来查看线程的守护状态。一个新的子线程会继承父线程的守护标记。整个python程序(也可以称作主线程)将在所有的非守护线程退出之后才退出。

    threading模块除了Thread类之外,还包括许多好用的同步机制:

    对象 描述
    Thread 表示一个执行线程的对象
    Lock 锁对象
    RLock 可重入锁对象,使单一线程可以(再次)获得已持有的锁(递归锁)
    Condition 条件变量对象,使得一个线程等待另外一个线程满足特定的条件,比如改变状态或者某个数据值
    Event  条件变量的通用版本,任意数量的线程等待某个事件的发生,在该事件发生后所有的线程都将被激活
    Semaphore 为线程间的有限资源提供一个计数器,如果没有可用资源时会被阻塞
    BoundedSemaphore 于Semaphore相似,不过它不允许超过初始值
    Timer 于Thread类似,不过它要在运行前等待一定时间
    Barrier 创建一个障碍,必须达到指定数量的线程后才可以继续

    其中,Thread类是threading模块的主要执行对象。

    下面是Thread类的属性和方法列表:

    属性 描述
    Thread类属性
    name 线程名
    ident 线程的标识符
    daemon 布尔值,表示这个线程是否是守护线程
    Thread类方法
    __init__(group=None,target=None,name=None,args=(),kwargs={},verbose=None,daemon=None) 实例化一个线程对象,需要一个可调用的target对象,以及参数args或者kwargs。还可以传递name和group参数。daemon的值将会设定thread.daemon的属性
    start() 开始执行该线程
    run() 定义线程的方法。(通常开发者应该在子类中重写)
    join(timeout=None) 直至启动的线程终止之前一直挂起;除非给出了timeout(单位秒),否则一直被阻塞
    getName() 返回线程名(该方法已被弃用)
    setName() 设定线程名(该方法已弃用)
    isAlive 布尔值,表示这个线程是否还存活(驼峰式命名,python2.6版本开始已被取代)
    isDaemon() 布尔值,表示是否是守护线程(已经弃用)
    setDaemon(布尔值) 在线程start()之前调用,把线程的守护标识设定为指定的布尔值(已弃用)

    使用Thread类,可以有多种方法创建线程:

    • 创建Thread类的实例,传递一个函数
    • 创建Thread类的实例,传递一个可调用的类实例
    • 派生Thread类的子类,并创建子类的实例

    一般的,我们会采用第一种或者第三种方法。如果需要一个更加符合面向对象的接口时,倾向于选择第三种方法,否则就用第一种方法吧。

    第一种方法:创建Thread类,传递一个函数

    下面的脚本中,我们先实例化Thread类,并传递一个函数(及其参数),当线程执行的时候,函数也会被执行:

    #!/usr/bin/env/ python
    
    import threading
    from time import sleep,ctime
    #不再把4秒和2秒硬性的编码到不同的函数中,而是使用唯一的loop()函数,并把这些常量放进列表loops中
    loops=[4,2]
    
    def loop(nloop,nsec):
        print('开始循环',nloop,'at:',ctime())
        sleep(nsec)
        print('循环',nloop,'结束于:',ctime())
        
    def main():
        print('程序开始于:',ctime())
        threads=[]
        nloops=range(len(loops))
        
        for i in nloops:
            t=threading.Thread(target=loop,args=(i,loops[i])) #循环 实例化2个Thread类,传递函数及其参数,并将线程对象放入一个列表中
            threads.append(t)
            
        for i in nloops:
            threads[i].start()  #循环 开始线程
            
        for i in nloops:
            threads[i].join()   #循环 join()方法可以让主线程等待所有的线程都执行完毕。
            
        print('任务完成于:',ctime())
        
    if __name__=='__main__':
        main()

    执行结果:

    PS C:UsersWC> python E:Python3.6.3workspacemtsleepC.py
    程序开始于: Thu Mar 29 21:35:13 2018
    开始循环 0 at: Thu Mar 29 21:35:13 2018
    开始循环 1 at: Thu Mar 29 21:35:13 2018
    循环 1 结束于: Thu Mar 29 21:35:15 2018
    循环 0 结束于: Thu Mar 29 21:35:17 2018
    任务完成于: Thu Mar 29 21:35:17 2018

    和上节的thread模块相比,不同点在于:实现同样的效果,thread模块需要锁对象,而threading模块的Thread类不需要。实例化Thread(调用Thread())和调用thread.start_new_thread()的最大区别就是新线程不会立即执行!这是一个非常有用的同步功能,尤其在我们不希望线程立即开始执行的时候。

      当所有的线程都分配完成之后,通过调用每个线程的start()方法再让他们开始。相比于thread模块的管理一组锁(分配、获取、释放检查锁状态)来说,threading模块的Thread类只需要为每个线程调用join()方法即可。join(timeout=None)方法将等待线程结束,或者是达到指定的timeout时间时。这种锁又称为自旋锁。

      最重要的是:join()方法,其实你根本不需要调用它。一旦线程启动,就会一直执行,直到给定的函数完成后退出。如果主线程还有其他事情要做(并不需要等待这些线程完成),可以不调用join()。join()只有在你需要等待线程完成时候才是有用的。

    例如上面的脚本中,我们注释掉join()代码:

    ……  
      for i in nloops:
            threads[i].start()  #循环 开始线程
        ''' 
        for i in nloops:
            threads[i].join()   #循环 join()方法可以让主线程等待所有的线程都执行完毕。
        '''    
        print('任务完成于:',ctime())
        
    if __name__=='__main__':
        main()

    运行结果:

    PS C:UsersWC> python E:Python3.6.3workspacemtsleepC.py
    程序开始于: Thu Mar 29 21:55:10 2018
    开始循环 0 at: Thu Mar 29 21:55:10 2018
    开始循环 1 at: Thu Mar 29 21:55:10 2018
    任务完成于: Thu Mar 29 21:55:10 2018
    循环 1 结束于: Thu Mar 29 21:55:12 2018
    循环 0 结束于: Thu Mar 29 21:55:14 2018

    我们发现:主线程的任务比两个循环线程要先执行(任务完成于……在 循环X结束……的前面)

    第二种方法:创建Thread类的实例,传递一个可调用的类实例

    创建线程时,于传入函数类似的方法是传入一个可调用的类的实例,用于线程执行——这种方法更加接近面向对象的多线程编程。比起一个函数或者从一个函数组中选择而言,这种可调用的类包含一个执行环境,有更好的灵活性。

    在上述的mtsleepC.py脚本中添加一个新类ThreadFunc,稍微改动一番,形成mtsleepD.py文件:

    #!/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):
            self.func(*self.args)
            
    def loop(nloop,nsec):
        print('开始循环',nloop,'在:',ctime())
        sleep(nsec)
        print('结束循环',nloop,'于:',ctime())
        
    def main():
        print('程序开始于:',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('任务完成于:',ctime())
        
    if __name__=='__main__':
        main()
        

    执行结果:

    PS C:UsersWC> python E:Python3.6.3workspacemtsleepD.py
    程序开始于: Thu Mar 29 22:30:02 2018
    开始循环 0 在: Thu Mar 29 22:30:02 2018
    开始循环 1 在: Thu Mar 29 22:30:02 2018
    结束循环 1 于: Thu Mar 29 22:30:04 2018
    结束循环 0 于: Thu Mar 29 22:30:06 2018
    任务完成于: Thu Mar 29 22:30:06 2018

    上述脚本中,主要添加了ThreadFunc类,并在实例化Thread对象时,通过传参的形式同时实例化了可调用类ThreadFunc。这里同时完成了两个实例化。

    我们研究一下创建ThreadFunc类的思想:我们希望这个类更加通用,而不是局限于loop()函数,为此,添加了一些新的东西,比如这个类保存了函数自身、函数的参数、以及函数名。构造函数__init__()用于设定上述值。当创建新线程的时候,Thread类的代码将调用ThreadFunc对象,此时会调用__call__()这个特殊方法。

    (老实说,这种方法显得有些尴尬,并且稍微难以阅读)

    第三种方法:派生Thread的子类,并创建子类的实例

    和方法二相比,方法三再创建线程时使用子类要相对更容易阅读,下面是mtsleepE.py脚本:

    #!/usr/bin/env pyhton
    
    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 run(self):
            self.func(*self.args)
            
    def loop(nloop,nsec):
        print('开始循环',nloop,'在:',ctime())
        sleep(nsec)
        print('结束循环',nloop,'于:',ctime())
        
    def main():
        print('程序开始于:',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('所有的任务完成于:',ctime())
        
    if __name__ =='__main__':
        main()

    运行结果:

    PS C:UsersWC> python E:Python3.6.3workspacemtsleepE.py
    程序开始于: Thu Mar 29 22:52:18 2018
    开始循环 0 在: Thu Mar 29 22:52:18 2018
    开始循环 1 在: Thu Mar 29 22:52:18 2018
    结束循环 1 于: Thu Mar 29 22:52:20 2018
    结束循环 0 于: Thu Mar 29 22:52:22 2018
    所有的任务完成于: Thu Mar 29 22:52:22 2018

    比较方法二和方法三,重要的变化在于:MyThread子类的构造函数必须先调用其父类的构造函数;重写run()方法,代替方法二中的__call__()方法。

    优化第三种方法:

    对MyThread类进行修改,增加一些调试信息的输出,并将该类单独存储为myThread.py的模块(MyThread.py),以便在以后的例子中需要的时候导入这个类。另外,除了简单的调用函数外,还可以将结果保存在实例的属性self.res中,同时增加新的方法getResult()来获取这个值:

    #!/usr/bin/env python
    
    import threading
    from time import ctime
    
    class MyThread(threading.Thread):
        def __init__(self,func,args,name=''):
            threading.Thread.__init__(self)
            self.func = func
            self.name = name
            self.args = args
            
        def run(self):
            print('开始执行',self.name,' 在:',ctime())
            self.res = self.func(*self.args)
            print(self.name,'结束于:',ctime())
            
        def getResult(self):
            return self.res

    下面,我们介绍多线程和单线程执行效果对比的时候,会用到这个MyThread.py模块。

    脚本mtfacfib.pt,比较了递归求斐波那契、阶乘和累计函数的执行,分别按照单线程和多线程的方式执行同样的任务:

    #!/usr/bin/env python
    
    from myThread import MyThread
    from time import ctime,sleep
    #斐波那契
    def fib(x):
        sleep(0.005)
        if x < 2:
            return 1
        return fib(x-1)+fib(x-2)
    #阶乘
    def fac(x):
        sleep(0.1)
        if x < 2:
            return 1
        return x*fac(x-1)
    #累加
    def sum(x):
        sleep(0.1)
        if x < 2 :
            return 1
        return x + sum(x-1)
        
    funcs=[fib,fac,sum]
    n = 12
    
    def main():
        nfuncs = range(len(funcs))
        
        #单线程
        print('单线程模式')
        for i in nfuncs:
            print('开始',funcs[i].__name__,' 在:',ctime())
            print(funcs[i](n))
            print(funcs[i].__name__,'结束于:',ctime())
            
        #多线程
        print('多线程模式')
        threads = []
        for i in nfuncs :
            t = MyThread(funcs[i],(n,),funcs[i].__name__)
            threads.append(t)
            
        for i in nfuncs:
            threads[i].start()
            
        for i in nfuncs:
            threads[i].join()
            print(threads[i].getResult())
            
        print('所有的任务结束')
        
    if __name__ == '__main__':
        main()
        

    运行结果:

    PS C:UsersWC> python E:Python3.6.3workspacemtfacfib.py
    单线程模式
    开始 fib  在: Fri Mar 30 14:08:43 2018
    233
    fib 结束于: Fri Mar 30 14:08:45 2018
    开始 fac  在: Fri Mar 30 14:08:45 2018
    479001600
    fac 结束于: Fri Mar 30 14:08:46 2018
    开始 sum  在: Fri Mar 30 14:08:46 2018
    78
    sum 结束于: Fri Mar 30 14:08:48 2018
    多线程模式
    开始执行 fib  在: Fri Mar 30 14:08:48 2018
    开始执行 fac  在: Fri Mar 30 14:08:48 2018
    开始执行 sum  在: Fri Mar 30 14:08:48 2018
    fac 结束于: Fri Mar 30 14:08:49 2018
    sum 结束于: Fri Mar 30 14:08:49 2018
    fib 结束于: Fri Mar 30 14:08:50 2018
    233
    479001600
    78
    所有的任务结束

    程序中,为了看到多线程如何改善性能的,我们加入了sleep函数用于减慢执行速度。

    看到单线程模式中,只是简单的一次调用每个函数,并在函数结束执行的时候立即显示相关的结果;而使用多线程的时候,并不会立刻显示结果,因为我们希望MyThread类越通用越好(有输出和无输出都能执行),我们一直等到所有线程都join之后,再调用getResult()方法显示每个函数的返回值。

  • 相关阅读:
    node
    github
    [模块] pdf转图片-pdf2image
    python 15 自定义模块 随机数 时间模块
    python 14 装饰器
    python 13 内置函数II 匿名函数 闭包
    python 12 生成器 列表推导式 内置函数I
    python 11 函数名 迭代器
    python 10 形参角度 名称空间 加载顺序
    python 09 函数参数初识
  • 原文地址:https://www.cnblogs.com/hiwuchong/p/8673183.html
Copyright © 2011-2022 走看看