文章目录
一、线程和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