15.3.1 建立多线程环境
多线程环境的建立,说得直白一点,主要就是创建GIL。我们已经知道GIL 对于Python的多线程机制的重要意义,然而这个GIL 到底是如何实现的呢,呃,这是一个很有趣的问题。
[pythread.h] |
终于见识到了神秘的GIL(interpreter_lock),没想到吧,万万没想到,它居然指示一个简单的void*。但是转念一想,在C 中void*几乎可以是任何东西,这家伙,可是个万能容器啊。
可以看到,无论创建多少个线程,Python 建立多线程环境的动作只会执行一次。在PyEval_InitThreads 的开始,Python 会检查GIL 是否已经被创建,如果是,则不再进行任何动作,否则,就会创建这个GIL。创建GIL 的工作由PyThread_allocate_lock 完成,我们来看一看这个GIL 到底是何方神圣。
[thread_nt.h] |
在这里,我们终于看到了Python 中多线程机制的平台相关性,在Python25\Python 目录下,有一大批thread_***.h 这样的文件,在这些文件中,包装了不同操作系统的原生线程,并通过统一的接口暴露给Python,比如这里的PyThread_allocate_lock 就是这样一个接口。我们这里的thread_nt.h 中包装的是Win32 平台的原生thread,在本章中后面的代码剖析中,还会有大量与平台相关的代码,我们都以Win32 平台为例。
在PyThread_allocate_lock 中,与PyEval_InitThreads 非常类似的,它会检查一个initialized 的变量,如果说GIL 指示着Python 的多线程环境是否已经建立,那么这个initialized 变量就指示着为了使用底层平台所提供的原生thread,必须的初始化动作是否完成。这些必须的初始化动作通常都是底层操作系统所提供的API,不同的操作系统可能需要不同的初始化动作。在Win32 平台下,不需要任何的初始化动作,所以PyThread_init_thread 的唯一作用就是设置initialized 变量:
[thread.c] |
[thread_nt.h] |
[thread_nt.h] |
一切真相大白了,原来,GIL(NRMUTEX)中的hevent 就是Win32 平台下的Event这个内核对象,而其中的thread_id 将记录任一时刻获得GIL 的线程的id。
到了这里,Python 中的线程互斥机制的真相渐渐浮出水面,看来Python 是通过Win32下的Event 来实现了线程的互斥,熟悉Win32 的朋友马上就可能想到,与这个Event 对应的,必定有一个WaitForSingleObject。
在PyEval_InitThreads 通过PyThread_allocate_lock 成功地创建了GIL 之后,当前线程就开始遵循Python 的多线程机制的规则:在调用任何Python C API 之前,必须首先获得GIL。因此PyEval_InitThreads 紧接着通过PyThread_acquire_lock 尝试获得GIL。
[thread_nt.h] |
PyThread_acquire_lock 有两种工作方式,通过函数参数waitflag 来区分。这个waitflag 指示当GIL 当前不可获得时,是否进行等待,更直接地说,就是当前线程是否通过WaitForSingleObject 将自身挂起,直到别的线程释放GIL,然后由操作系统将自己唤醒。
如果waitflag 为0,Python 会检查当前GIL 是否可用,GIL 中的owned 是指示GIL是否可用的变量,在前面的InitializeNonRecursiveMutex 中我们看到这个值被初始化为-1,Python 会检查这个值是否为-1,如果是,则意味着GIL 可用,必须将其置为0,当owned 为0 后,表示该GIL 已经被一个线程占用,不再可用。对于我们这里分析的调用PyEval_InitThread 的主线程而言, 由于在初始化GIL 之后就调用PyThread_acquire_lock 申请GIL,到这时,并没有第二个线程被创建,所以主线程会轻而易举地获得GIL 的使用权。
注意这里的检查和更新owned 的操作是通过一个Win32 的系统API——Interlocked-CompareExchange——来完成的。这个API 是一个原子操作,其函数原形和功能如下。
原形:InterlockedCompareExchange |
与InterlockedCompareExchange 相同的,InterlockedIncrement 也是一个原子操作,其功能是将mutex->owned 的值增加1。从这里可以看到,当一个线程开始等待GIL时,其owned 就会被增加1。显然我们可以猜测,当一个线程最终释放GIL 时,一定会将GIL 的owned 减1,这样当所有需要GIL 的线程都最终释放了GIL 之后,owned 会再次变为-1,意味着GIL 再次变为可用。
为了清晰地展示这一点,我们现在就来看看PyThread_aquire_lock 的逆运算,PyThread_release_lock 每一个将从运行转态转为等待状态的线程都会在被挂起之前调用它以释放对GIL 的占有。
[thread_nt.h] [thread_nt.h] |
最终,一个线程在释放GIL 时,会通过SetEvent 通知所有在等待GIL 的hevent 这个Event 内核对象的线程,结合前面的分析,如果这时候有线程在等待GIL 的hevent,那么将被操作系统唤醒。这就是我们在前面介绍的Python 将线程调度的第二个难题委托给操作系统来实现的机制。
到了这时,调用PyEval_InitThread 的线程(也就是Python 主线程)已经成功获得了GIL,最后会调用PyThread_get_thread_ident(),通过Win32 的API:GetCurrent-ThreadId,获得当前Python 主线程的id,并将其赋给main_thread,main_thread 是一个静态全局变量,专职存储Python 主线程的线程id,用以判断一个线程是否是Python 主线程。
最后,我们在图15-4 中给出整个PyEval_InitThread 的函数调用关系。
|
| 图15-4 PyEval_InitThreads 中的 函数调用关系 |
| 回书目 上一节 下一节 |
|
||||
| · 无线路由器故障处理 · 解析35岁技术人的价值.. · 无线重中之重:安全问题 · 无线局域网基本知识 · 家庭无线局域网 · 华为七千人主动辞职规.. · 微软出价446亿美元收购.. · 虚拟化的“赤壁之战” |
· Windows Server 2008专.. · 802.11n:下一代的无线.. · 脉冲无线电uwb专题 · AIX操作系统管理应用 · 云计算时代来临 · 求职必杀技 决战面试官 · 龙芯要做中国的“奔腾” · 2008年上半年IT技术图.. |
|||
|
||||
| · SOA 面向服务架构 · SQL Server 2008/2005.. · Apache技术专题 · 三层交换技术专题 · SQL Server入门到精通 · 无线网状网(MESH) · Windows远程桌面应用 · C#技术开发指南 |
· Apache技术专题 · Windows集群服务应用 · C#技术开发指南 · 文档格式标准开战 OOXM.. · 路由器设置与口令恢复 · Linux 集群技术专题 · PHP开发应用手册 · SOA 面向服务架构 |
|||
|
||||
| · SQL Server入门到精通 · SQL Server 2008/2005.. · SOA 面向服务架构 · Apache技术专题 · C#技术开发指南 · 三层交换技术专题 · Apache技术专题 · C#技术开发指南 |
· Windows远程桌面应用 · 企业数据恢复指南 · Windows集群服务应用 · 路由器设置与口令恢复 · Linux 集群技术专题 · SOA 面向服务架构 · 了解统一威胁管理(UTM).. · 解析35岁技术人的价值.. |
|||