15.4 Python 线程的调度
15.4.1 标准调度
当主线程和子线程都进入了Python 解释器之后,Python 的线程之间的切换就完全由Python 的线程调度机制掌控。Python 的线程调度机制是内建在Python 的解释器核心PyEval_EvalFrameEx 中的。在分析Python 字节码解释器的框架时,我们曾给出过一个PyEval_EvalFrameEx 的框架结构,但是在那里,并没有给出线程调度机制的实现,下面列出的是加入了线程调度机制的PyEval_EvalFrameEx 的框架结构(见代码清单15-4)。
代码清单15-4
[ceval.c] |
[ceval.c] |
PyEval_EvalFrameEx 每执行一条字节码指令,_Py_Ticker 就将减少1;当执行了_Py_CheckInterval 条指令之后,_Py_Ticker 将减少到0,这就将进入线程调度。注意,主线程和子线程都将调用PyEval_EvalFrameEx,所以这里的描述可能会引起混乱,为了保证读者清晰地了解线程调度时所发生的一切,我们先来回忆一下thread1.py 现在所处的情景。
主线程获得了GIL,并且正在执行PyEval_EvalFrameEx 函数的代码,这时子线程在t_bootstrap 中调用PyEval_AcquireThread。通过调用PyThrad_acquire_lock 申请GIL,但是由于GIL 被主线程占有,所以子线程被挂起。
现在整个Python 的进程中,只有一个活动的线程:主线程。主线程不断执行字节码,_Py_Ticker 的值不断减少,当_Py_Ticker 的值减少到0 时,主线程在代码清单15-4 的[1]处首先将维护当前线程状态对象的_PyThreadState_Current 设置为NULL,然后释放掉GIL。注意啦,注意啦,转折点到了:这时,由于等待GIL 而被挂起的子线程被操作系统的线程调度机制唤醒,从而最终进入PyEval_EvalFrameEx。对于主线程,注意,虽然这时它已经失去了GIL,但是由于它没有被挂起,所以对于操作系统的线程调度机制,它是可以被再次切换为活动线程的。
当操作系统的调度机制将主线程切换为活动线程之后,主线程将执行代码清单15-4的[2],在PyThread_acquire_lock 中,主线程申请GIL,由于被子线程占有,主线程将自身挂起。从这时开始,操作系统的线程调度不能再将主线程切换为活动进行,除非等到子线程释放GIL 之后。而子线程进入PyEval_EvalFrameEx 之后,开始如之前主线程一般的行为,在执行了_Py_CheckInterval 条指令之后,子线程将执行代码[1],释放GIL,由此唤醒主线程。而子线程在继续执行[2]时,又将因等待GIL 将自身挂起,如此反复,直至永恒,从而完整地在Python 中实现了对多线程机制的支持。
有一点需要特别注意,实际上,Python 并非在执行了_Py_CheckInterval 条指令之后开始线程调度。从PyEval_EvalFrameEx 的代码框架以及本书第2 部分对Python 虚拟机的剖析可以看到,很多字节码执行之后都会通过goto 转移到fast_next_opcode 处继续执行,这时是不会更新_Py_Ticker 的值的;而有的字节码在执行之后则会转移到最外层的for 循环,这时是会更新_Py_Ticker 的。Python 在这一方面有一种柔性,并非立下了军令状。
下面我们通过修改Python 源代码来观察Python 虚拟机进行线程调度的情形。在ceval.c中,我们声明一个全局的整形变量counter,用于记录实际执行的字节码指令。在PyEval_EvalFrameEx 的dispatch_opcode 标记位置之后,添加代码:++counter。注意,由于Python 虚拟机在执行一些字节码,尤其是涉及条件判断的字节码时,会发生直接跳跃的动作,所以这里counter 记录的也并非真实的字节码数量,只是最接近真实字节码数量的一个值。在PyEval_EvalFrameEx 的判断“_Py_Ticker < 0”成立之后,输出counter 的值,并将counter 重新归0。为了观察标准调度,我们先将thread1.py 中的两条time.sleep语句注释掉,thread1.py 的运行结果如图15-9 所示。
可以看到,在标准调度下,在两个切换线程的输出语句之间,只有一个线程的输出结果,要么是主线程的输出结果,要么是子线程的输出结果。这充分说明了,标准调度是让一个线程充分执行,直到激发Python 的模拟时钟中断(_Py_Ticker < 0),另一个线程才能有机会执行。同时,在输出结果中,我们发现每次发生线程切换时,输出的counter的计数结果是不定的,这与前面的分析非常吻合。
|
| 图15-9 标准调度下的线程切换 |
| 回书目 上一节 下一节 |
|
||||
| · 无线路由器故障处理 · 解析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岁技术人的价值.. |
|||