PHP8.1の新機能紹介 Fibers

並行処理を書きやすくする機構。
ドキュメントによると

Libraries will generally build further abstractions around Fibers, so there’s no need to interact with them directly.

https://www.php.net/releases/8.1/en.php#fibers

と書かれていて、ライブラリを作る人以外は直接使うことはないとのこと。
(逆に言えばCoroutineやPromiseを使うようなライブラリを作る人は使う機会ありそう)

Fibersを使ってListenするとなると次のようなコードになるかなと思われます。

<?php

$address = '0.0.0.0';
$port = 3080;
if (($sock = stream_socket_server("tcp://{$address}:{$port}", $errno, $errstr)) === false) {
    echo "stream_socket_server() failed: reason: {$errstr} {$errno}" . PHP_EOL;
    exit(255);
}



define('QUIT_MSG', 'QUIT');
class socket_manager{
    public static $connections = 0; 
    static function create_fiber($conn){
        stream_set_blocking($conn, false);
        $fiber = new Fiber(function () use ($conn): void {
            while(true){
                $resume_msg = Fiber::suspend('fiber');// resume()後この次の行から実行される
                echo "Value used to resume fiber: ", $resume_msg, PHP_EOL;
                if($resume_msg === QUIT_MSG){
                    disconnect_client($conn);
                    return;
                }
                $tmp_conn_list = [$conn];
                $write_conn = null;
                $expect = null;
                $is_recv = stream_select($tmp_conn_list, $write_conn, $expect, 0, 0);
                if($is_recv === false){
                    echo 'stream_select error'.PHP_EOL;
                    disconnect_client($conn);
                    return;
                }
                $recv = stream_get_contents($conn);
                if($recv === false ){
                    echo 'stream_get_contents error'.PHP_EOL;
                    return;
                }
                /*if($recv === ''){
                    echo 'no data'.PHP_EOL;
                    continue;
                }*/
                echo "received {$recv}".PHP_EOL;
                $res = fwrite($conn, $recv."\r\n");// echo response
                if($res === false){
                    static::disconnect_client($conn);
                    return;
                }
            }
        });

        $value = $fiber->start();
        echo "Value from fiber suspending: ", $value, PHP_EOL;
        return $fiber;
    }
    static function disconnect_client($conn){
        fclose($conn);
        echo "$conn disconnected.".PHP_EOL;
    }
}

while(true){
    if (($conn = stream_socket_accept($sock)) === false) {
        socket_close($sock);
        exit(255);
    }
    echo "connected. current connections: {socket_manager::$connections}".PHP_EOL;
    $fiber = socket_manager::create_fiber($conn);

    while(true){
        usleep(200000);// 200msごとにfiberをresumeする
        echo 'main loop start'.PHP_EOL;
        if(!$fiber->isSuspended()){
            break;
        }
        $fiber->resume(date('Y-m-d\TH:i:s\Z'));// Fiber::suspend()した次の行から実行
    }
    echo 'main loop end'.PHP_EOL;
    unset($fiber);
}
socket_close($sock);

Fibersといえども下記の図のようにシングルスレッドなのであまり実用的ではないですが、threadやforkと組み合わせれば使いどころがありそうです。

Fiber execution flow
c.f. https://wiki.php.net/rfc/fibers
カテゴリー: php

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です