python核心编程(第三版) 第四章 -多线程编程

版权声明:本文为博主原创文章,欢迎转载。 https://blog.csdn.net/fww330666557/article/details/82899048

一、线程和python

1、全局解释器锁

Python代码的执行是由Python虚拟机(又名解释器主循环)进行控制的;
在主循环中同时只能有一个控制线程在执行;
内存中可以有许多程序,但是在任意给定时刻只能有一个程序在运行。
尽管Python解释器中可以运行多个线程,但是在任意给定时刻只有一个线程会被解释器执行。
对 Python虚拟机的访问是由**全局解释器锁(GIL)**控制的。这个锁就是来保证同时只能有一个线程运行的。
当调用外部代码(即,任意C/C++扩展的内置函数)时,GIL会保持锁定,直至函数执行结束。

2、退出线程

当一个线程完成函数的执行时它就会退出。你还可以通过调用诸如thread.exit()之类的退出函数,或者sys.exit()之类的退出python进程的标准方法,亦或抛出SystemExit异常,来使线程退出。不过,你不能直接“终止”一个线程。

3、在Python中使用线程

Python虽然支持多线程,但是还需要取决于它所运行的操作系统。绝大多数的类UNIX平台以及Windows平台都是支持多线程的。
Python使用兼容POSIX的线程,也就是众所周知的pthread。

4、不使用线程的情况

使用但线程执行循环:

#!/usr/bin/env python3
#-*- coding:utf-8 -*-

'''
使用单线程执行循环
'''

__author__ = 'TaoGuan'

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())
    loop0()
    loop1()
    print('all DONE at:',ctime())
if __name__ == '__main__':
    main()

运行结果:

starting at: Sat Sep 29 09:50:06 2018
start loop 0 at: Sat Sep 29 09:50:06 2018
loop 0 DONE at: Sat Sep 29 09:50:10 2018
start loop 1 at: Sat Sep 29 09:50:10 2018
loop 1 DONE at: Sat Sep 29 09:50:12 2018
all DONE at: Sat Sep 29 09:50:12 2018

理论上,该程序执行完成至少需要6秒,从运行结果看,符合理论预期。那么,loop0盒loop1能不能并行执行呢?答案是肯定的。

二、thread模块

1、使用thread

#!/usr/bin/env python3
#-*- coding:utf-8 -*-

'''
使用htread模块
'''

__author__ = 'TaoGuan'
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()

运行:

fwwdeair:4 fww$ python3 mtsleepA.py
starting at: Sat Sep 29 12:36:05 2018
start loop 0 at: Sat Sep 29 12:36:05 2018
start loop 1 at: Sat Sep 29 12:36:05 2018
loop 1 DONE at: Sat Sep 29 12:36:07 2018
loop 0 DONE at: Sat Sep 29 12:36:09 2018
all DONE at: Sat Sep 29 12:36:11 2018

a、start_new_thread()必须包含开始的两个参数,于是即使执行的函数不需要参数,也需要传递一个空元组。
从执行结果可以看出:
b、原来需要执行至少6秒的程序,现在只需要4秒了;
c、loop0和loop1几乎是同时开始执行的,而因为loop0需要执行的时间比loop1长,因此loop1先执行完成。也就是说,两个函数是并行执行的。
d、主函数中为什么要加一个sleep(6)呢?这是因为如果我们没有阻止主线程继续执行,它将会继续执行下一条语句,显示“all DONE”,然后退出,而loop0和loop1这两个循环将直接终止。而之所以是6秒,是因为我们所有的任务肯定会在6秒之内完成。

2、使用线程和锁

上一个程序,之所以我们能知道所有任务会在6秒内结束,是因为我们没有做实质的工作,只是简单滴sleep了几秒而已,但是对于实际的任务,我们很难确切知道它需要执行多少时间,因此我们必须改进这个机制。

#!/usr/bin/env python3
#-*- coding:utf-8 -*-

'''
使用线程和锁
'''

__author__ = 'TaoGuan'
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', nloop, 'done at:',ctime())
    lock.release()

def main():
    print('starting at:',ctime())
    locks = []
    nloops = range(len(loops))
    # 创建锁列表
    for i in loops:
        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()

运行结果:

fwwdeair:4 fww$ python3 mtsleepB.py
starting at: Sat Sep 29 14:44:18 2018
start loop 0 at: Sat Sep 29 14:44:18 2018
start loop 1 at: Sat Sep 29 14:44:18 2018
loop 1 done at: Sat Sep 29 14:44:20 2018
loop 0 done at: Sat Sep 29 14:44:22 2018
all DONE at: Sat Sep 29 14:44:22 2018

可见,结果正如我们预期。

三、threading模块

thread模块我们应尽量避免使用,而应使用更高级的threading模块。
threading模块的Thread类是主要的执行对象。
使用Thread类有很多方法来创建线程,这里主要介绍三种:

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

1、创建Thread类实例,传给它一个函数

#!/usr/bin/env python3
#-*- coding:utf-8 -*-

'''
创建Thread类实例,传给它一个函数
'''

__author__ = 'TaoGuan'

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()

运行结果:

fwwdeair:4 fww$ python3 mtsleepC.py
starting at: Sat Sep 29 15:05:33 2018
start loop 0 at: Sat Sep 29 15:05:33 2018
start loop 1 at: Sat Sep 29 15:05:33 2018
loop 1 done at: Sat Sep 29 15:05:35 2018
loop 0 done at: Sat Sep 29 15:05:37 2018
all DONE at: Sat Sep 29 15:05:37 2018

结果正如预期。
与上一个例子对比的几点不同:
a、使用thread模块时实现的锁没有了,取而代之的是一组Thread对象;
b、实例化Thread和调用thread.start_new_thread()最大的区别是新线程不会立即执行;
c、所有线程分配完成后,调用start()方法让它们开始执行;
d、相比于管理一组锁(分配、获取、释放、检查锁状态等)而言,这里只需要为每个线程调用join()方法即可。join()方法将等待线程结束,或者在提供了超时时间的情况下,达到超时时间。这种锁,又叫做自旋锁。
e、对join()方法而言,另一个重要方面是其实它根本不需要调用。一旦线程启动,它们就会一直执行,直到给定的函数完成后退出。如果主线程还有其它事情去做,而不是等待这些线程完成,就可以不调用join().join()方法只有在你需要等待线程完成时候才是有用的。

2、创建Thread类实例,传给它一个可调用的类实例

#!/usr/bin/env python3
#-*- coding:utf-8 -*-

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

__author__ = 'TaoGuan'

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('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()

运行结果:

fwwdeair:4 fww$ python3 mtsleepD.py
starting at: Sat Sep 29 15:24:25 2018
start loop 0 at: Sat Sep 29 15:24:25 2018
start loop 1 at: Sat Sep 29 15:24:25 2018
loop 1 done at: Sat Sep 29 15:24:27 2018
loop 0 done at: Sat Sep 29 15:24:29 2018
all DONE at: Sat Sep 29 15:24:29 2018

结果正如预期。

3、派生Thread的子类,并创建子类的实例

#!/usr/bin/env python3
#-*- coding:utf-8 -*-

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

__author__ = 'TaoGuan'

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('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()

运行结果:

fwwdeair:4 fww$ python3 mtsleepE.py
starting at: Sat Sep 29 15:32:31 2018
start loop 0 at: Sat Sep 29 15:32:31 2018
start loop 1 at: Sat Sep 29 15:32:31 2018
loop 1 done at: Sat Sep 29 15:32:33 2018
loop 0 done at: Sat Sep 29 15:32:35 2018
all DONE at: Sat Sep 29 15:32:35 2018

结果如预期。
几点重要的改变:
a、Mythread子类的构造函数(init)必须先调用其基类的构造函数;
b、之前的特殊方法__call__在这个子类中必须要写为run().

Thread子类MyThread改进(myThread.py):

#!/usr/bin/env python3
#-*- coding:utf-8 -*-

'''
创建Thread类子类
'''

__author__ = 'TaoGuan'

import threading
from time import ctime

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 = self.func(*self.args)
        print(self.name, 'finished at:', ctime())

为了让Thread的子类更加通用,将该子类移到一个专门的模块中,并添加了可调用的getResult()方法来取得返回值。

测试程序:

#!/usr/bin/env python3
#-*- coding:utf-8 -*-

'''
创建myThread类测试程序
'''

__author__ = 'TaoGuan'

import threading
from time import sleep, ctime
import myThread

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 = myThread.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()

测试程序是自己照猫画虎搞的,先凑合吧。
运行结果:

192:4 fww$ python3 myThreadTest.py
starting at: Sat Sep 29 15:56:38 2018
starting loop at: Sat Sep 29 15:56:38 2018
start loop 0 at: Sat Sep 29 15:56:38 2018
starting loop at: Sat Sep 29 15:56:38 2018
start loop 1 at: Sat Sep 29 15:56:38 2018
loop 1 done at: Sat Sep 29 15:56:40 2018
loop finished at: Sat Sep 29 15:56:40 2018
loop 0 done at: Sat Sep 29 15:56:42 2018
loop finished at: Sat Sep 29 15:56:42 2018
all DONE at: Sat Sep 29 15:56:42 2018

猜你喜欢

转载自blog.csdn.net/fww330666557/article/details/82899048