毫秒精度的定时器。底层基于 epoll_wait
和 setitimer
实现,数据结构使用最小堆
,可支持添加大量定时器。
在同步 IO 进程中使用 setitimer
和信号实现,如 Manager
和 TaskWorker
进程
在异步 IO 进程中使用 epoll_wait
/kevent
/poll
/select
超时时间实现
底层使用最小堆数据结构实现定时器,定时器的添加和删除,全部为内存操作,因此性能是非常高的。
官方的基准测试脚本 timer.php 中,添加或删除
10
万个随机时间的定时器耗时为0.08s
左右。
~/workspace/swoole/benchmark$ php timer.php add 100000 timer :0.091133117675781s del 100000 timer :0.084658145904541s
定时器是内存操作,无 IO
消耗
Timer
与 PHP
本身的 pcntl_alarm
是不同的。pcntl_alarm
是基于时钟信号 + tick
函数实现存在一些缺陷:
最大仅支持到秒,而 Timer
可以到毫秒级别
不支持同时设定多个定时器程序
pcntl_alarm
依赖 declare(ticks = 1)
,性能很差
底层不支持时间参数为 0
的定时器。这与 Node.js
等编程语言不同。在 Swoole
里可以使用 Swoole\Event::defer 实现类似的功能。
Swoole\Event::defer(function () { echo "hello\n"; });
上述代码与 JS
中的 setTimeout(func, 0)
效果是完全一致的。
tick()
、after()
、clear()
都拥有一个函数风格的别名
类静态方法 | 函数风格别名 |
---|---|
Swoole\Timer::tick() | swoole_timer_tick() |
Swoole\Timer::after() | swoole_timer_after() |
Swoole\Timer::clear() | swoole_timer_clear() |
设置一个间隔时钟定时器。
与 after
定时器不同的是 tick
定时器会持续触发,直到调用 Timer::clear 清除。
Swoole\Timer::tick(int $msec, callable $callback_function, ...$params): int
1. 定时器仅在当前进程空间内有效
2. 定时器是纯异步实现的,不能与同步 IO 的函数一起使用,否则定时器的执行时间会发生错乱
3. 定时器在执行的过程中可能存在一定误差
参数
功能:给执行函数传递数据【此参数也为可选参数】
默认值:无
其它值:无
功能:时间到期后所执行的函数,必须是可以调用的
默认值:无
其它值:无
功能:指定时间
值单位:毫秒【如 1000
表示 1
秒,v4.2.10
以下版本最大不得超过 86400000
】
默认值:无
其它值:无
int $msec
callable $callback_function
...$params
可以使用匿名函数的 use
语法传递参数到回调函数中
$callback_function 回调函数
callbackFunction(int $timer_id, ...$params);
功能:由 Timer::tick
传入的第三个参数 $param
默认值:无
其它值:无
功能:定时器的 ID
【可用于 Timer::clear 清除此定时器】
默认值:无
其它值:无
int $timer_id
...$params
扩展
定时器校正
定时器回调函数的执行时间不影响下一次定时器执行的时间。实例:在 0.002s
设置了 10ms
的 tick
定时器,第一次会在 0.012s
执行回调函数,如果回调函数执行了 5ms
,下一次定时器仍然会在 0.022s
时触发,而不是 0.027s
。
但如果定时器回调函数的执行时间过长,甚至覆盖了下一次定时器执行的时间。底层会进行时间校正,丢弃已过期的行为,在下一时间回调。如上面例子中 0.012s
时的回调函数执行了 15ms
,本该在 0.022s
产生一次定时回调。实际上本次定时器在 0.027s
才返回,这时定时早已过期。底层会在 0.032s
时再次触发定时器回调。
协程模式
在协程环境下 Timer::tick
回调中会自动创建一个协程,可以直接使用协程相关 API
,无需调用 go
创建协程。
可设置 enable_coroutine 关闭自动创建协程
使用示例
Swoole\Timer::tick(1000, function(){ echo "timeout\n"; });
正确示例
Swoole\Timer::tick(3000, function (int $timer_id, $param1, $param2) { echo "timer_id #$timer_id, after 3000ms.\n"; echo "param1 is $param1, param2 is $param2.\n"; Swoole\Timer::tick(14000, function ($timer_id) { echo "timer_id #$timer_id, after 14000ms.\n"; }); }, "A", "B");
错误示例
Swoole\Timer::tick(3000, function () { echo "after 3000ms.\n"; sleep(14); echo "after 14000ms.\n"; });
在指定的时间后执行函数。Swoole\Timer::after
函数是一个一次性定时器,执行完成后就会销毁。
此函数与 PHP
标准库提供的 sleep
函数不同,after
是非阻塞的。而 sleep
调用后会导致当前的进程进入阻塞,将无法处理新的请求。
Swoole\Timer::after(int $msec, callable $callback_function, ...$params): int
参数
功能:给执行函数传递数据【此参数也为可选参数】
默认值:无
其它值:无
功能:时间到期后所执行的函数,必须是可以调用的。
默认值:无
其它值:无
功能:指定时间
值单位:毫秒【如 1000
表示 1
秒,v4.2.10
以下版本最大不得超过 86400000
】
默认值:无
其它值:无
int $msec
callable $callback_function
...$params
可以使用匿名函数的 use 语法传递参数到回调函数中
返回值
执行成功返回定时器 ID
,若取消定时器,可调用 Swoole\Timer::clear
扩展
协程模式
在协程环境下 Swoole\Timer::after 回调中会自动创建一个协程,可以直接使用协程相关 API
,无需调用 go
创建协程。
可设置 enable_coroutine 关闭自动创建协程
使用示例
$str = "Swoole"; Swoole\Timer::after(1000, function() use ($str) { echo "Hello, $str\n"; });
使用定时器 ID
来删除定时器。
Swoole\Timer::clear(int $timer_id): bool
参数
功能:定时器 ID
【调用 Timer::tick、Timer::after 后会返回一个整数的 ID】
默认值:无
其它值:无
int $timer_id
Swoole\Timer::clear
不能用于清除其他进程的定时器,只作用于当前进程
使用示例
$timer = Swoole\Timer::after(1000, function () { echo "timeout\n"; }); var_dump(Swoole\Timer::clear($timer)); var_dump($timer); // 输出:bool(true) int(1) // 不输出:timeout
清除当前 Worker 进程内的所有定时器。
Swoole 版本 >= v4.4.0
可用
Swoole\Timer::clearAll(): bool
返回 timer
的信息。
Swoole 版本 >= v4.4.0
可用
Swoole\Timer::info(int $timer_id): array
返回值
array(5) { ["exec_msec"]=> int(6000) ["exec_count"]=> // v4.8.0 添加 int(5) ["interval"]=> int(1000) ["round"]=> int(0) ["removed"]=> bool(false) }
返回定时器迭代器,可使用 foreach
遍历当前 Worker 进程内所有 timer
的 id
Swoole 版本 >= v4.4.0
可用
Swoole\Timer::list(): Swoole\Timer\Iterator
使用示例
foreach (Swoole\Timer::list() as $timer_id) { var_dump(Swoole\Timer::info($timer_id)); }
查看定时器状态。
Swoole 版本 >= v4.4.0
可用
Swoole\Timer::stats(): array
返回值
array(3) { ["initialized"]=> bool(true) ["num"]=> int(1000) ["round"]=> int(1) }
设置定时器相关参数。
Swoole\Timer::set(array $array): void
此方法从 v4.6.0
版本标记为废弃。
默认定时器在执行回调函数时会自动创建协程,可单独设置定时器关闭协程。
swoole_async_set([ 'enable_coroutine' => false, ]);