这篇文章是讲解PHP7虚拟机中断的用法,理解这篇文章之后就可以更好的理解Swoole在CPU密集型的情况下协程的调度原理。
Swoole之前尝试了多种方法来处理CPU密集型环境下协程的切换问题。分别是Hook for循环,使用PHP的declare(ticks=N)语法。因为种种原因,Swoole现在使用了虚拟机中断的方法来切换一直占用CPU的协程。具体原因可以查看PHP最近的Issue以及PR,里面写的很清楚了。
好的,我们现在来感受一下虚拟机中断的用法。
首先,我们实现一个扩展函数,它的作用是开启对虚拟机中断的使用:
1 | PHP_FUNCTION(start_interrupt) { |
init的实现如下:
1 | void init() |
这里的核心是
1 | zend_interrupt_function = new_interrupt_function; |
也就是说,我们需要实现zend_interrupt_function。zend_interrupt_function的作用是在虚拟机中断发生的时候,会去执行的函数。并且,zend_interrupt_function是PHP内核定义的一个函数指针。
(我们这篇文章不多讲orig_interrupt_function = zend_interrupt_function;,我们假设没有其他扩展实现了zend_interrupt_function)
new_interrupt_function的定义如下:
1 | static void new_interrupt_function(zend_execute_data *execute_data) |
可以看出,我们的这个函数'模拟'了yield协程的过程。OK,我们接着看create_scheduler_thread:
1 | static void create_scheduler_thread() |
这个函数的作用是创建一个线程,这个线程的执行体是schedule函数:
1 | void schedule() |
而这个schedule线程我们可以认为它是一个负责调用的一个线程。它设置EG(vm_interrupt)的值为1。设置完之后,当虚拟机检查到这个值为1的时候,就会去执行new_interrupt_function函数,从而实现了yield协程。
每次触发完虚拟机中断后,虚拟机会把EG(vm_interrupt)设置为0。因此,这里需要循环的去设置EG(vm_interrupt)的值为1。为什么这里需要使用usleep呢?因为中断不是每分每秒都在进行的,所以可以挂起这个线程,让其他线程跑。
好的,我们来写一段PHP脚本来测试一下:
1 |
|
1 | ~/codeDir/phpCode/test # php interrupt.php |