• 《python核心编程》读书笔记--第18章 多线程编程


    18.1引言

    在多线程(multithreaded,MT)出现之前,电脑程序的运行由一个执行序列组成。多线程对某些任务来说是最理想的。这些任务有以下特点:它们本质上就是异步的,需要多个并发事务,各个事务的运行顺序可以是不确定的、随机的、不可预测的。这样的编程任务可以被分成多个执行流,每个流都有一个要完成的目标。根据应用的不同,这些子任务可能都要计算出一个中间结果,用于合并得到最后的结果。运算密集型任务一般都比较容易分割为多个子任务。

    由于顺序执行的程序只有一个线程在运行,它要保证做多个任务,并且不会有某个任务占用太多时间。执行多任务的顺序执行的程序一般程序控制流程都很复杂,难以理解。

    使用多线程编程和一个共享的数据结构比如Queue,这种程序任务可以用几个功能单一的线程来组织。

    18.2线程和进程

    进程

    进程是程序的一次执行。每个进程都有自己的地址空间、内存、数据栈及其他记录其运行轨迹的辅助数据。进程都有自己的内存空间、数据栈等,所以只能使用进程间通讯(interprocess communication,IPC),而不能直接共享信息。

    线程

    线程跟进程类似,不同的是,所有的线程运行在同一个进程中,共享相同的运行环境。一个进程的各个线程之间共享一片数据空间,所以线程之间可以比进程之间更方便地共享数据以及相互通讯。但是也有副作用,就是同一片数据,在多个线程访问顺序不同时,可能导致数据不一致问题,当然这是可以解决的。

    18.3Python、线程和全局解释器锁

    1、全局解释器锁(GIL)

    Python代码的执行由Python虚拟机(也称解释器主循环)控制。在一个主循环中,只有一个线程在执行,与单核CPU类似,虽然Python解释器可以运行“多个”线程,但是在任意时刻,只有一个线程在解释器中运行。

    以上原理由全局解释器锁(GIL)控制,保证同一时刻只有一个线程在运行。在多线程环境中,Python虚拟机按以下方式执行。

    1)设置GIL

    2)切换到一个线程去运行

    3)运行:

        a.指定数量自己码的指令,或者

        b.线程主动让出控制(time.sleep())

    4)把线程设置为睡眠状态

    5)解锁GIL

    6)再次重复以上所有步骤。

    调用外部扩展(c程序等)时,GIL被锁定,直到这个函数结束为止。

    2、退出线程

    当一个线程结束计算,就退出了。线程可以调用thread.exit()之类的退出函数,也可以用python退出的标准方法,如sys.exit()或者抛出一个SystemExit异常等。不过不可以直接kill一个线程。

    thread和threading两个模块进行进程、线程管理,作者建议不用thread,一个很明显的原因是:thread模块主线程退出时,所有其他线程没有被清除就退出了;threading就能确保所有“重要”的子线程都退出后,程序才会结束。

    3、下面看一下单线程

    #-*- coding:utf-8 -*-
    from time import sleep,ctime
    
    def loop0():
        print 'start loop0 at:',ctime()
        sleep(4)
        print 'loop0 done at:',ctime()
    
    def loop1():
        print 'start loop1 at:',ctime()
        sleep(2)
        print 'loop1 done at:',ctime()
    
    def main():
        print 'starting at:',ctime()
        loop0()
        loop1()
        print 'all DONE at:',ctime()
    
    if __name__ == '__main__':
        main()

    当第一个循环执行完之后才会继续执行第二个循环。

    4、模块

    python提供的几个多线程编程模块,包括thread、threading、Queue等。thread和threading允许我们创建和管理线程。thread提供了基本的线程和锁的支持,而threading提供了更高级别,功能更强的线程管理功能。Queue允许用户创建可以用于多个线程之间共享数据的队列数据结构。

    作者再次强调不用thread而是用threading。

    18.4Thread模块

    两个例子

    #-*- coding:utf-8 -*-
    from time import sleep,ctime
    import thread
    
    def loop0():
        print 'start loop0 at:',ctime()
        sleep(4)
        print 'loop0 done at:',ctime()
    
    def loop1():
        print 'start loop1 at:',ctime()
        sleep(2)
        print 'loop1 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()

    上面问题的解决方法就是引进锁。

    #-*- coding:utf-8 -*-
    from time import sleep,ctime
    import thread
    
    loops = [4,2]
    
    def loop(nloop,nsec,lock):
        print 'start loop',nloop,' at:',ctime()
        sleep(nsec)
        print 'loop',nloop,' done at:',ctime()
        lock.release()   #一个线程运行结束后释放,通知主线程已经结束
    
    def main():
        print 'starting at:',ctime()
        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()

    18.5Threading模块

    threading模块中将提供Thread类来实现多线程,并且提供了很好的同步机制。thread模块不支持守护线程,即主线程退出时,不管是否有子线程,都会被强行退出。而threading模块有机制来避免上面的问题。

    1、thread类

    利用thread类可以有多种方法来创建线程。有三种方法:

    • 创建一个Thread类的实例,传给它一个函数
    • 创建一个Thread类的实例,传给它一个可调用的类的对象
    • 从Thread派生出一个子类,创建一个这个子类的实例

    作者推荐最后一种。

    第一个例子

    实例化一个Thread类,传入一个函数。

    #-*- coding:utf-8 -*-
    from time import sleep,ctime
    import threading
    
    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:
            #下面实例化Thread类,实例化之后并不马上开始(直到调用start),这样可以更好的同步
            t = threading.Thread(target = loop,args = (i,loops[i]))
            threads.append(t)
    
        for i in nloops:
            threads[i].start()
        
        for i in nloops:
            #线程挂起,直到运行结束
            #调用join另一个重要的方面就是它可以完全不用调用,一旦线程启动就会一直运行知道结束
            #如果主线程有其他事情做(不止是等待结束),就不用调用join,只有等待结束时才调用join
            threads[i].join()  
    
        print 'all DONE at:',ctime()
    
    if __name__ == '__main__':
        main()

    第二个例子

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

    #-*- coding:utf-8 -*-
    from time import sleep,ctime
    import threading
    
    loops = [4,2]
    
    class ThreadFunc(object):
        """docstring for THreadFunc"""
        def __init__(self, func,args,name = ''):
            super(ThreadFunc, self).__init__()
            self.name = name
            self.func = func
            self.args = args
    
        #为了可以使Thread调用,需要定义call函数,用来执行func函数
        def __call__(self):
            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:
            #下面实例化Thread类,接收的是一个类实例
            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 的实例,传给它一个可调用的类对象。

    #-*- coding:utf-8 -*-
    from time import sleep,ctime
    import threading
    
    loops = (4,2)
    #print dir(threading.Thread) 查看threading.Thread类中的方法,有run
    class MyThread(threading.Thread):
        """docstring for THreadFunc"""
        def __init__(self, func,args,name = ''):
            super(MyThread, self).__init__()
            self.name = name
            self.func = func
            self.args = args
    
        #注意这里的run函数重新定义
        def run(self):
            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:
            #下面实例化MyThread类
            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()

    看上面的三个例子,确实感觉第三个更灵活和方便。但是话说回来,本质上还是第一个例子的灵魂加上类的外衣。

    下面是一个斐波那契数列的例子。比较单线程和多线程的区别。

    先自定义一个MyThread类:

    #-*- coding:utf-8 -*-
    from time import sleep,ctime
    import threading
    
    
    class MyThread(threading.Thread):
        """docstring for THreadFunc"""
        def __init__(self, func,args,name = ''):
            super(MyThread, self).__init__()
            self.name = name
            self.func = func
            self.args = args
    
        def getResult(self):
            return self.res
    
        #注意这里的run函数重新定义
        def run(self):
            print 'starting',self.name,'at:',ctime()
            self.res = self.func(*self.args)
            print self.name,'finished at:',ctime()

    下面是三个数值例子。

    #-*- coding:utf-8 -*-
    from time import sleep,ctime
    from myThread import MyThread
    
    #Fibonacci数列
    def fib(x):
        sleep(0.005)
        if x < 2:return 1
        return (fib(x-2) + fib(x-1))
    
    #阶乘
    def fac(x):
        sleep(0.1)
        if x < 2:return 1
        return (x * fac(x-1))
    
    #累加和
    def summ(x):
        sleep(0.1)
        if x < 2:return 1
        return (x + summ(x-1))
    
    funcs = [fib,fac,summ]
    n = 12
    
    def main():
        nfuncs = range(len(funcs))
    
        print '*** SINGLE THREAD'
        for i in nfuncs:
            print 'starting',funcs[i].__name__,'at:',ctime()
            print funcs[i](n)
            print funcs[i].__name__,'finished at:',ctime()
    
        print '
    *** MULTIPLE THREADS'
        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 'all DONE.'
    
    if __name__ == '__main__':
        main()
     
    >>>
    *** SINGLE THREAD
    starting fib at: Thu Feb 25 14:49:18 2016
    233
    fib finished at: Thu Feb 25 14:49:20 2016
    starting fac at: Thu Feb 25 14:49:20 2016
    479001600
    fac finished at: Thu Feb 25 14:49:21 2016
    starting summ at: Thu Feb 25 14:49:21 2016
    78
    summ finished at: Thu Feb 25 14:49:22 2016

    *** MULTIPLE THREADS
    starting fib at: Thu Feb 25 14:49:22 2016
    starting fac at: Thu Feb 25 14:49:22 2016
    starting summ at: Thu Feb 25 14:49:22 2016
    facsumm  finished at:finished at: Thu Feb 25 14:49:24 2016
    Thu Feb 25 14:49:24 2016
    fib finished at: Thu Feb 25 14:49:25 2016
    233
    479001600
    78
    all DONE.
    [Finished in 7.4s]

    可以看出,多线程确实节省了一些时间,但是由于MyThread类的构造,打印顺序有点混乱。另,斐波那契数列用递归太耗时,可以看一下这里的最后部分 http://www.cnblogs.com/batteryhp/p/4987261.html。令,上面的几个for可以用in函数直接运行,不用算长度再取下标。

    2、生产者-消费者问题和Queue模块

    Queue模块用来进行线程间的通讯,让各个线程之间共享数据。

    #-*- coding:utf-8 -*-
    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)  #向队列中put进一个
        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) #定义最大长度为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()
    >>>
    starting writer at: Thu Feb 25 15:48:39 2016
    producing object for Q... None
    size now 1
    starting reader at: Thu Feb 25 15:48:39 2016
    consumed object from Q... size now 0
    producing object for Q... None
    size now 1
    producing object for Q... None
    size now 2
    consumed object from Q... size now 1
    producing object for Q... None
    size now 2
    producing object for Q... None
    size now 3
    consumed object from Q... size now 2
    writer finished at: Thu Feb 25 15:48:49 2016
    consumed object from Q... size now 1
    consumed object from Q... size now 0
    reader finished at: Thu Feb 25 15:49:02 2016
    all DONE.
    [Finished in 23.3s]

    由于没有用到锁,所以打印的时候乱七八糟。。。

  • 相关阅读:
    Problem about objc_exception_throw
    Change mime Types for mac's default apache
    重启Mac上的Apache服务
    通过无线网安装自己打包的ipa文件
    How to cancel selection for TreePanel(GXT)
    使用Jsoup获取网页内容超时设置
    HTML Meta, http-equiv, Refresh
    线上redis服务内存异常分析。
    C++/CLI 本地字符串和托管字符串之间的转换
    DWF Toolkit on Microsoft Windows
  • 原文地址:https://www.cnblogs.com/batteryhp/p/5215170.html
Copyright © 2020-2023  润新知