PHP轉Go系列 | 毫秒級別定時器的使用姿勢
說到定時器大家第一時間想到的肯定是 Crontab 計劃任務,利用 Linux 操作系統(tǒng)的定時調度來執(zhí)行我們寫的業(yè)務腳本,但是它只能精確到秒級別,無法具體到毫秒級別的調度。如果說利用 PHP 的 Sleep 函數(shù)來實現(xiàn)調度,也只能到秒級別。那我們想要實現(xiàn)毫秒級別的調度就沒有辦法了嗎?辦法還是有的,PHP 異步高性能通信擴展 Swoole 中就提供了毫秒級別的定時器,我們可以安裝 Swoole 擴展來予以支持。但是有些朋友會覺得安裝 Swoole 擴展略顯麻煩,那還有沒有其他的方式呢?我的回答是「有」,這一次我就利用 PHP 中事件循環(huán) Event 來實現(xiàn)毫秒級別的定時器,當然我也還會使用 Go 語言來實現(xiàn)一次毫秒級別定時器,順便對比一下兩者之間的區(qū)別。
剛剛提到使用 PHP 中的事件循環(huán) Event 來實現(xiàn)毫秒級別的定時器,那么我們直接看下面這段代碼。首先是初始化 EventConfig 事件配置類,然后再構造一個事件基礎對象 $event_base,再創(chuàng)建一個 Event 事件對象,之后 add 添加到事件循環(huán)之中,最后執(zhí)行 loop 事件循環(huán)操作。其中需要指定事件類型為 Event::TIMEOUT,如果只指定 Event::TIMEOUT 則會只回調執(zhí)行一次,如果要持續(xù)執(zhí)行還需指定 Event::PERSIST 類型。在剛才我們也說到 Swoole 中也實現(xiàn)了毫秒級別定時器,其實不瞞你說,它的實現(xiàn)原理也是基于 Event 事件循環(huán)來實現(xiàn)的,本質原理都是一致的,如果有疑惑的朋友可以去看看 Swoole 的官方文檔。
<?php
/**
 * 在多少毫秒之后執(zhí)行
 */
function timer_after(int $msec, callable $callback){
    // 初始化一個 EventConfig
    $event_config = new EventConfig();
    // 初始化一個 EventBase
    $event_base = new EventBase($event_config);
    // 初始化一個定時器事件
    $timer = new Event($event_base, -1, Event::TIMEOUT, $callback);
    // 將定時器添加到事件循環(huán)
    $timer->add($msec/1000);
    // 進入事件循環(huán)狀態(tài)
    $event_base->loop();
}
/**
 * 每間隔多少毫秒執(zhí)行一次
 */
function timer_tick(int $msec, callable $callback_function){
    // 初始化一個 EventConfig
    $event_config = new EventConfig();
    // 初始化一個 EventBase
    $event_base = new EventBase($event_config);
    // 初始化一個定時器事件
    // Event::PERSIST 表示該事件將持續(xù)存在,一旦被觸發(fā)會再次激活
    $timer = new Event($event_base, -1, Event::TIMEOUT | Event::PERSIST, $callback_function);
    // 將定時器添加到事件循環(huán)
    $timer->add($msec/1000);
    // 進入事件循環(huán)狀態(tài)
    $event_base->loop();
}
// 設定為 500 毫秒之后執(zhí)行
timer_after(500, function(){
    echo microtime(true) . " 只執(zhí)行一次" . PHP_EOL;
    // 這里可以執(zhí)行具體的業(yè)務邏輯...
});
// 每間隔 500 毫秒執(zhí)行一次
timer_tick(500, function(){
    echo microtime(true) . " 每隔一段時間執(zhí)行一次" . PHP_EOL;
    
    // 這里可以執(zhí)行具體的業(yè)務邏輯...
});看過了 PHP 中的實現(xiàn)方式,我們再來看看在 Go 語言中是怎樣實現(xiàn)毫秒級別定時器的。從下面這段代碼中可以看出,直接利用 time 標準庫中的 Ticker 便可以實現(xiàn)毫秒級別定時器的功能,首先使用 NewTicker 創(chuàng)建了一個定時器,然后使用 select case 語句來監(jiān)聽 ticker.C 通道的數(shù)據(jù),如果時間到了則會觸發(fā),之后便可執(zhí)行延時后的業(yè)務邏輯,最后一定要記得調用 ticker.Stop() 停止定時器。其中還想要持續(xù)觸發(fā)的話,只需要在外圍加上 for 循環(huán)就可以實現(xiàn)間隔執(zhí)行。在這個例子中其實也可以看出 Go 語言中的定時器是基于 Channel 通道的超時來實現(xiàn)的,感興趣的朋友可以翻開源碼看一眼。
package test
import (
"time"
"fmt"
)
// 在指定時間之后只執(zhí)行一次
func TestTickerAfter(t *testing.T) {
 // 設置 500 毫秒之后執(zhí)行
 interval := 500 * time.Millisecond
 // 創(chuàng)建定時器
 ticker := time.NewTicker(interval)
 // 確保在程序結束前停止定時器
 defer ticker.Stop()
 select {
case t := <-ticker.C: // 等待定時器觸發(fā)
  fmt.Printf("只執(zhí)行一次 %v\n", t.Local().UnixMilli())
  // 這里可以執(zhí)行具體的業(yè)務邏輯...
 }
}
// 每間隔一段時間執(zhí)行一次
func TestTickerTick(t *testing.T) {
 // 設置每間隔 500 毫秒之后執(zhí)行
 interval := 500 * time.Millisecond
 // 創(chuàng)建定時器
 ticker := time.NewTicker(interval)
 // 確保在程序結束前停止定時器
 defer ticker.Stop()
for {
  select {
case t := <-ticker.C: // 等待定時器觸發(fā)
   fmt.Printf("每間隔一段時間執(zhí)行一次 %v\n", t.Local().UnixMilli())
   // 這里可以執(zhí)行具體的業(yè)務邏輯...
  }
 }
}從上面的兩個例子可以看出 PHP 和 Go 中對毫秒定時器的實現(xiàn)原理是有所不同的,PHP 中是基于 Event 事件循環(huán)來實現(xiàn)的,這種回調式的實現(xiàn)方式在實際的編程中不順手,有些朋友可能會不太能適應,但是也沒有更好的方式了。那在 Go 中是基于 Channel 通道的超時機制來實現(xiàn)的,這種同步的編程方式會更符合編程的習慣。不過我個人認為,畢竟是兩門不同的語言,每種語言都有自己的風格,我們還是應該要求同存異,不應過分苛求。本次分享的內容就到這里了,希望對大家能有所幫助。















 
 
 





 
 
 
 