15.4.2 阻塞调度
标准调度是在Python 执行完了可执行的指令条数之后才发生的,但是在实际中,Python 需要支持另一种触发线程调度的情形,我们称之为阻塞调度。其基本思想是:在线程A 通过某种操作,比如说等待输入,将自身阻塞后,Python 应该将等待GIL 的线程B唤醒。
考虑我们的thread1.py,在主线程和子线程中都有time.sleep(1)的调用。假如子线程调用了time.sleep(1),那么子线程将释放GIL,挂起自身,Python 唤醒主线程。同样,在主线程中调用time.sleep(1),也将使Python 唤醒子线程。这展示了一种情形,即程序有时候希望将自己挂起。
除了这种线程主动放弃GIL 的情况之外,还有另一种情形,即程序不得不挂起。还是考虑我们的thread1.py,假如在主线程的thread.start_new_thread(threadProc, ())之后我们调用raw_input(),那么主线程必须等待用户的输入,这时,主线程也不得不释放GIL,将自身挂起。
我们通过研究time.sleep()来剖析Python 是如何实现这种阻塞调度的。在Python中,time module 在timemodule.c 中实现,而其中的sleep 操作由time_sleep 函数,进而由floatsleep 函数实现。
[timemodule.c] |
实际上,Sleep 机制也是平台相关的,这里我们只展示了Win32 平台下的sleep 实现。同时,由于Win32 平台下的sleep 实现也比较复杂,我们关注的焦点并不是timemodule的实现,而是阻塞调度机制是如何实现的,所以我们只列出了子线程调用sleep 时涉及的相关代码。我们看到,Python 实际上是通过调用Win32 的系统API:sleep 来实现了阻塞的机制。那么在调用sleep 之前,子线程肯定需要将GIL 释放。在floatsleep 中,我们注意到在调用Sleep 之前,有一个Py_BEGIN_ALLOW_THREADS,与之对应的,在调用Sleep之后,还有一个Py_END_ALLOW_THREADS,正是这两个宏完成了触发Python 进行线程调度的工作。
[ceval.h] |
在Py_BEGIN_ALLOW_THREADS 这个宏定义的代码中,子线程释放了GIL,这将唤醒等待GIL 的主线程;而在Py_END_ALLOW_THREADS 宏所定义的代码中,子线程重新申请GIL。注意,在子线程调用了Py_BEGIN_ALLOW_THREAD 之后,它就不再受GIL 的约束。从这时开始,Python 的两个线程都可能被操作系统的线程调度机制选中,直到子线程通过Py_END_ALLOW_THREADS 申请GIL 为止,Python 又恢复为只能有一个线程被操作系统的线程调度机制选中。
这意味着Python 的线程在某种情况下可以脱离GIL 的控制,然而我们看到,在Py_BEGIN_ALLOW_THREAD 和Py_END_ALLOW_THREADS 之间,子线程并没有调用任何Python 的C API,只是调用了操作系统的API,这不会导致共享资源的访问冲突,所以依然是线程安全的。开始的时候我们就说过,在理论上,Python 并不是一定要GIL 这样的解释器级的互斥线程的机制,只要能保护共享资源即可,而当前Python 采用的GIL 只是多种线程互斥机制中的一种而已。
同样,对于raw_input 而言,其最终将由PyOS_Readline 实现,我们最终也会在PyOS_Readline 中发现Py_BEGIN_ALLOW_THREAD 和Py_END_ALLOW_THREADS 联袂的身影。Python 正是通过这两个宏实现了阻塞调度机制。
有趣的是,在线程通过阻塞调度切换时,Python 内部的那个_Py_Ticker 依然会被保持,并不会被重置为100,只有标准调度才会重置这个Python 的模拟时钟。在图15-10 中,清晰地显示了这一结果。注意,这时需要将thread1.py 中的两条time.sleep 语句打开,以便激发阻塞调度。
|
| 图15-10 阻塞调度与标准调度结合下的线程切换 |
从图15-10 中我们看到阻塞调度确实是独立于标准调度另一种线程调度机制。而阻塞调度确实没有重置_Py_Ticker,否则Python 显示出来的值决不会是137 这样小的值了。需要注意的,图15-10 和图15-9 中的输出从120 多增长到了130 多,是因为图15-9 对应的例子中我们只使用了标准调度,图15-10 中则混合使用了标准调度和阻塞调度,这多出来的10 几条正是Python 执行time.sleep()时所消耗的指令数。
| 回书目 上一节 下一节 |



























