Skip to content

Latest commit

 

History

History
144 lines (98 loc) · 4.13 KB

《PHP扩展开发》-协程-sleep(四).md

File metadata and controls

144 lines (98 loc) · 4.13 KB

sleep(四)

这一篇文章,我们开始来完善一下我们的PHPCoroutine::scheduler方法。

首先我们来回顾一下在Coroutine::sleep里面的代码:

uv_timer_t timer;
timer.data = co;
uv_timer_init(uv_default_loop(), &timer);
uv_timer_start(&timer, sleep_timeout, seconds * 1000, 0);

我们发现,这个代码段不足以开启定时器,这个仅仅是把定时器节点插入了定时器堆里面。如果我们要启动这个定时器,我们还需要执行uv_run。但是,我们不可以在Coroutine::sleep里面去调用uv_run,因为一旦我们调用了这个方法,那么,我们就会立马进入libuv的事件循环,这显然是不合理的。所以,我们需要自己去实现出uv_run的效果。而这个效果,我们就在PHPCoroutine::scheduler里面去实现它。

OK,我们现在来实现一下。

我们修改PHPCoroutine::scheduler这个方法的代码:

typedef enum {
    UV_CLOCK_PRECISE = 0,  /* Use the highest resolution clock available. */
    UV_CLOCK_FAST = 1      /* Use the fastest clock with <= 1ms granularity. */
} uv_clocktype_t;

extern "C" void uv__run_timers(uv_loop_t* loop);
extern "C" uint64_t uv__hrtime(uv_clocktype_t type);
extern "C" int uv__next_timeout(const uv_loop_t* loop);

int PHPCoroutine::scheduler()
{
    uv_loop_t* loop = uv_default_loop();

    while (loop->stop_flag == 0)
    {
        loop->time = uv__hrtime(UV_CLOCK_FAST) / 1000000;
        uv__run_timers(loop);

        if (uv__next_timeout(loop) < 0)
        {
            uv_stop(loop);
        }
    }

    return 0;
}

因为libuv在它src目录下面的头文件不会被安装在/usr/local/include这个目录项目,所以,如果我们不重复定义uv_clocktype_t以及extern这三个函数(uv__run_timersuv__hrtimeuv__next_timeout),那么在编译的时候编译器就会说没有声明这些函数。这是我使用libuv比较头疼的一件事情。

我们来分析一下这段代码,其中:

uv_loop_t* loop = uv_default_loop();

用来获取到libuv这个库里面定义好的全局变量loop,我们可以通过这个变量来控制循环。

while (loop->stop_flag == 0)

用来判断循环是否结束。

loop->time = uv__hrtime(UV_CLOCK_FAST) / 1000000;

修改当前的时间。

uv__run_timers(loop);

这个函数会遍历整个定时器堆,让我们设置的每个定时器节点时间和loop->time进行比较,如果这个定时器节点的时间大于了这个loop->time,也就意味着定时器过期了,这个时候,就会去执行这个定时器节点的回调函数。而这个回调函数是我们自己设置的,我们可以回顾一下我们写好的Coroutine::sleep代码:

uv_timer_start(&timer, sleep_timeout, seconds * 1000, 0);

其中sleep_timeout就是我们的回调函数:

static void sleep_timeout(uv_timer_t *timer)
{
    ((Coroutine *) timer->data)->resume();
}

这个函数的作用就是去resume当前协程,于是就起到了唤醒当前协程的效果。

我们继续分析PHPCoroutine::scheduler的代码。

if (uv__next_timeout(loop) < 0)
{
 		uv_stop(loop);
}

这段代码的作用是先执行uv__next_timeout来判断一下是否还有为执行的定时器,如果没有,那么会返回-1,然后执行uv_stoploop->stop_flag设置为1,结束我们最外层的while循环。

OK,我们来重新编译、安装一下:

~/codeDir/cppCode/study # make clean && make && make install

然后编写测试脚本:

<?php

$t1 = time();

$cid = Sgo(function () {
    echo "before sleep" . PHP_EOL;
    SCo::sleep(1);
    echo "after sleep" . PHP_EOL;
});

echo "main co" . PHP_EOL;

SCo::scheduler();

执行结果如下:

~/codeDir/cppCode/study # php test.php 
before sleep
main co
after sleep
~/codeDir/cppCode/study # 

我们会发现输出main co之后会阻塞1s当前协程,然后再输出after sleep。这样,我们就实现完了协程的sleep接口。

下一篇:sleep(五)