全局解释锁
python 代码的执行是由 python 虚拟机(又称解释器主循环)进行控制的.Python 在设计时是这样考虑的,在主循环中同时只能有一个控制线程在执行,就像单核 CPU 中的多线程一样.内存中可以有许多程序,但是在任意给定时刻只能有一个程序在执行.同理,尽管 python 解释器中可以运行多个线程,但是在给定任意时刻只能有一个线程会被解释器执行.
对 python 虚拟机的访问是由全局解释器锁(GIL)控制的.这个锁就是用来保证同时,只能有一个线程运行的.
在多线程环境中.python 虚拟机将按照下面所述的方式执行:
1. 设置GIL
2. 切换进一个线程取运行
3 .执行下面操作之一
a. 指定数量的字节码指令
b.线程主动让出控制权(可以调用 time.sleep(0) 来完成)
4.把线程设置回睡眠状态(切换出线程)
5.解锁GIL
6.重复上述步骤
当调用外部代码(即,任意 C/C++ 扩展内置函数)时,GIL 会保持锁定,直至函数执行结束(因为在这期间没有 Python 字节码计数).编写扩展函数的程序员有能力解锁 GIL,然而,作为 Python 开发者,你并不需要担心 Python 代码会在这些情况下被锁住.例如,对于任意面向 I/O 的 Python 例程 (调用了内置操作系统 C 代码的那种).
GIL 会在 I/O 调用前被释放,以允许其他线程在 I/O 执行的时候运行.而对于那些没有太多 I/O 操作的代码而言,更倾向于在该线程整个时间片内始终占有处理器和 GIL .换句话说,I/O 密集型的 Python 程序要比计算密集型的代码更好的利用多线程环境.
如果你对 Python 源代码,解释器主循环和 GIL 感兴趣,可以看看 Python/ceval.c 文件.
退出线程
当一个线程完成函数的执行时,它就会退出.另外,还可以通过诸如 thread.exit() 之类的退出函数,或者 sys.exit() 之类的退出 Python 进程的标准方法,或者抛出 SystenExit 异常,来使线程退出.不过,你不能直接 "终止" 一个线程.
下一节将会讨论两个与线程有关的 Python 模块,不过在这两个模块中,不建议使用 thread 模块.给出这个建议有很多原因,其中最明显的一个原因是在主线程退出后,所有其它线程都会在没有被清理前的情况下直接退出.而另一个模块 threading 会确保在所有 "重要的" 子线程退出前,保持整个进程的存活.
而主线程应该做一个好的管理者,负责了解每个单独的线程执行什么,每个派生的线程需要哪些数据或参数,这些线程执行完成后会提供什么样的后果.这样,主线程就可以收集每个线程的结果,然后汇总成一个有意义的结果.
在 Python 中使用线程
python 虽然支持多线程编程,但还是需要取决于它所运行的操作系统.
默认情况下,从源码构建的 Python(2.0 及以上的版本)或者 Win32 二进制安装的 Python,线程支持是已经启用的.要确定你的解释器是否支持线程,只需要从 Shell 窗口中尝试导入 thread 模块即可,如下所示(如果线程是可用的,则不会产生错误).
>>>import thread
>>>
如果你的 Python 解释器没有将线程支持编译进去,模块导入将会失败.
>>>import thread
Traceback (innermost last):
File "<stdin>", line 1, in?
ImportError: No module named thread
这种情况下你需要重新编译你的 Python 解释器才能够使用线程.一般可以调用 configure 脚本的时候使用 --with-thread 选项.查阅你所使用的发行版本的 README 文件,来获取如何在你的操作系统中编译线程支持的 Python 的指定命令.
不使用线程的情况
下面代码将使用 time.sleep() 函数来演示线程是如何工作的.`time.sleep() 函数需要一个浮点型的参数,然后以这个给定的秒数进行 "睡眠",也就是说,程序的执行会暂时停止指定的时间.
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()
创建两个时间循环:一个睡眠 4 秒(loop0()); 另一个睡眠 2 秒(loo1p()).如果在一个单进程或者单线程的程序中顺序执行 loop0() 和 loop1(),整个执行时间会达到 6 秒钟.而在启动 loop0() 和 loop1() 以及执行其它代码时,也可能存在一秒钟的开销,使得整个时间达到 7 秒.
可以通过执行代码验证这一点,下面是输出结果.
starting at: Tue Jan 23 09:48:47 2018
start loop 0 at: Tue Jan 23 09:48:47 2018
loop 0 done at: Tue Jan 23 09:48:51 2018
start loop 1 at: Tue Jan 23 09:48:51 2018
loop 1 done at: Tue Jan 23 09:48:53 2018
all Done at: Tue Jan 23 09:48:53 2018
现在,假设 loop0() 和 loop1() 中的操作不是睡眠,而是执行独立计算操作的函数,所有结果汇成一个最终结果.那么,让它们并行执行来减少总的执行时间是不是有用呢?这就是现在要介绍的多线程编程的前提.
Python 的 threading 模块
Python 提供了多个模块来支持多线程编程.包括,thread,threading,Queue 模块等.程序是可以提供 thread,threading 模块来创建于管理线程.thread 模块提供基本的线程和锁定支持;而 threading 模块提供更高级,功能更加全面的线程管理.使用 Queue 模块,用户可以创建一个队列数据结构,用于在多线程之间进行共享.我们将分别查看这几个模块,并给出几个例子和中等规模的应用.
核心提示:避免使用 thread 模块
原因是 threading 模块更加先进,有更好的线程支持,并且 thread 模块中的一些属性和 threading 模块有冲突.另一个原因是低级别的 thread 模块拥有的同步原语很少(实际上只有一个),而 threading 模块则有很多.
不过处于对 Python 和线程学习的兴趣,我们将给出使用使用 thread 模块的一些代码,给出这些代码只是出于学习目的, 希望它能够更好的让你领悟为什么应该避免使用 threading 模块.我们还将展示如何使用更加合适的工具,如 threading 和 Queue 模块中的那些方法.
避免使用 thread 模块的另一个原因是它对于进程何时退出没有控制,当主线程结束时,其他线程也都强制结束,不会发出警告或者进行适当的清理.如前所述,至少 threading 模块能确保重要的子线程在进程退出前结束.
来源: http://www.jianshu.com/p/be6363539622