这篇文章是讲解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 |