【PHP8.x】curl_multi_select関数の使い方

作成日: 更新日:

curl_multi_select関数は、複数のcURLリクエストを同時に処理する際に、データが利用可能になるのを効率的に待つために使用される関数です。この関数は、指定されたcURLマルチハンドルが管理している複数の接続(ソケット)の状態に変化がないかを監視します。具体的には、データが読み込み可能になったり、書き込み準備が整ったり、例外的な状態が発生したりするまで、指定された時間(タイムアウト)まで待機します。

PHPにおける非ブロッキングI/O(入出力)処理、特に複数のネットワーク通信を並行して行う場合において、各通信の状態を効率的に管理するためにこの関数が利用されます。例えば、複数のWeb APIに同時にリクエストを送信し、それぞれの応答を待つようなシナリオで役立ちます。個々の接続の状態を繰り返し確認するポーリング方式と比較して、この関数を使用することで、システムリソースを無駄に消費することなく、実際のデータ転送の準備が整うまで待つことができます。

通常、第一引数には監視対象のcURLマルチリソース(ハンドル)を指定し、第二引数には最大待機時間(秒単位)を渡します。関数は、準備が整ったソケットの数を整数で返しますが、タイムアウトした場合は0を、エラーが発生した場合は-1を返します。このように、curl_multi_select関数は、大量のネットワーク処理を効率的かつスムーズに実行するための重要なツールとして機能します。

基本的な使い方

構文(syntax)

<?php
$multi_handle = curl_multi_init();
$select_result = curl_multi_select($multi_handle, 0.5);
curl_multi_close($multi_handle);
?>

引数(parameters)

CurlMultiHandle $multi_handle, float $timeout = 1.0

  • CurlMultiHandle $multi_handle: 複数のcURL転送を管理するためのリソースハンドル
  • float $timeout = 1.0: 待機する最大秒数(浮動小数点数)

戻り値(return)

int

curl_multi_select関数は、処理可能な接続ハンドルの数を返します。この値は、次に選択できる接続があるかどうかを示します。

サンプルコード

PHP cURLマルチセレクトで非同期取得する

<?php

/**
 * 複数のURLからデータを並行して取得します。
 * curl_multi_select 関数を使用して、データが利用可能になるまで効率的に待機します。
 *
 * @param array $urls キーとURLのペアの配列 (例: ['task1' => 'http://example.com/api/data1'])
 * @return array 各URLの取得結果を含む連想配列
 */
function fetch_urls_concurrently(array $urls): array
{
    // マルチ cURL ハンドルを初期化します。
    // これにより、複数の cURL リクエストを同時に管理できるようになります。
    $multi_handle = curl_multi_init();
    if ($multi_handle === false) {
        // 初期化に失敗した場合、エラーメッセージを出力し空の配列を返します。
        echo "エラー: curl_multi_init の初期化に失敗しました。\n";
        return [];
    }

    $curl_handles = []; // 各URLに対応するcURLハンドルの配列を格納
    $results = [];       // 各URLの取得結果を格納する配列

    // 各URLに対して個別の cURL ハンドルを作成し、設定します。
    foreach ($urls as $id => $url) {
        $ch = curl_init();
        if ($ch === false) {
            echo "エラー: URL '{$url}' の cURL ハンドルの初期化に失敗しました。\n";
            continue; // 次のURLへ進みます
        }
        curl_setopt($ch, CURLOPT_URL, $url);             // リクエストするURLを設定
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  // 取得したデータを文字列として返すように設定
        // 必要に応じて、他のcURLオプションもここで設定できます。
        // 例: curl_setopt($ch, CURLOPT_TIMEOUT, 10); // タイムアウトを10秒に設定

        // 作成したcURLハンドルをマルチ cURL ハンドルに追加します。
        // これで、このハンドルが並行処理の対象となります。
        curl_multi_add_handle($multi_handle, $ch);
        $curl_handles[$id] = $ch; // 後で結果を取得するためにハンドルを保存
    }

    $running_handles = null; // 現在アクティブなcURLハンドルの数を追跡するための変数

    // 全てのcURL転送が完了するまでループします。
    do {
        // curl_multi_exec は、マルチハンドル内の全ての cURL ハンドルの転送を実行します。
        // 第2引数 ($running_handles) には、まだ転送中のハンドルの数が設定されます。
        $status = curl_multi_exec($multi_handle, $running_handles);

        // curl_multi_exec がエラーを返した場合
        if ($status !== CURLM_OK) {
            echo "エラー: curl_multi_exec の実行中にエラーが発生しました: " . curl_multi_strerror($status) . "\n";
            break;
        }

        // まだ転送中のハンドルがある場合 (つまり、$running_handles が 0 より大きい場合)
        if ($running_handles > 0) {
            // curl_multi_select は、データが利用可能になるまでブロック (待機) します。
            // これにより、CPU使用率を抑えつつ、効率的に待機できます。
            // タイムアウト値 (ここでは 1.0 秒) は、最大で待機する時間を指定します。
            // 戻り値は準備ができたハンドルの数、-1 はエラー、0 はタイムアウトを示します。
            $select_result = curl_multi_select($multi_handle, 1.0); // 最大1秒待機
            if ($select_result === -1) {
                echo "エラー: curl_multi_select の実行中にエラーが発生しました。\n";
                break; // エラーが発生したためループを抜けます
            } elseif ($select_result === 0) {
                // タイムアウトしましたが、まだデータは利用可能になっていない場合。
                // この場合でも、次の curl_multi_exec を呼び出す必要があります。
                // 必要であればここにログなどを出力できますが、通常は処理を続行します。
            }
        }
    } while ($running_handles > 0); // アクティブなハンドルがなくなるまでループを続行

    // 全ての転送が完了した後、各 cURL ハンドルの結果を取得します。
    foreach ($curl_handles as $id => $ch) {
        $error_code = curl_errno($ch); // cURLハンドルのエラーコードを取得

        if ($error_code === 0) {
            // エラーがない場合、取得したコンテンツとHTTPステータスコードを取得します。
            $content = curl_multi_getcontent($ch);
            $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $results[$id] = [
                'status' => 'success',
                'http_code' => $http_code,
                'content' => $content,
            ];
        } else {
            // エラーがある場合、エラーメッセージとコードを記録します。
            $error_message = curl_error($ch);
            $results[$id] = [
                'status' => 'error',
                'message' => $error_message,
                'error_code' => $error_code,
            ];
        }

        // 各 cURL ハンドルをマルチハンドルから削除し、閉じます。
        // これにより、リソースが解放されます。
        curl_multi_remove_handle($multi_handle, $ch);
        curl_close($ch);
    }

    // 最後に、マルチ cURL ハンドルを閉じます。
    curl_multi_close($multi_handle);

    return $results;
}

// --- サンプル使用方法 ---

// 複数のURLを定義します。テスト用にダミーのAPIエンドポイントを含めます。
$urls_to_fetch = [
    'user_data_1' => 'https://jsonplaceholder.typicode.com/todos/1',
    'user_data_2' => 'https://jsonplaceholder.typicode.com/todos/2',
    'post_data_1' => 'https://jsonplaceholder.typicode.com/posts/1',
    'invalid_url' => 'https://example.com/non_existent_page_12345', // 存在しないURLの例
];

echo "複数のURLからデータを非同期で取得を開始します...\n";
$start_time = microtime(true); // 処理開始時刻を記録

$fetched_data = fetch_urls_concurrently($urls_to_fetch);

$end_time = microtime(true); // 処理終了時刻を記録
echo "データの取得が完了しました。処理時間: " . round($end_time - $start_time, 2) . "秒\n\n";

// 取得したデータを表示します。
foreach ($fetched_data as $task_id => $data) {
    echo "--- タスクID: {$task_id} ---\n";
    if ($data['status'] === 'success') {
        echo "ステータス: 成功\n";
        echo "HTTPコード: " . $data['http_code'] . "\n";
        // コンテンツが長い場合があるので、先頭100文字だけ表示します。
        echo "コンテンツ (先頭100文字): " . substr($data['content'], 0, 100) . "...\n";
    } else {
        echo "ステータス: エラー\n";
        echo "エラーメッセージ: " . $data['message'] . " (コード: " . $data['error_code'] . ")\n";
    }
    echo "\n";
}

?>

curl_multi_select関数は、複数のcURLリクエストを並行して実行する際に、データ転送の準備が整うまで効率的に待機するための関数です。これは、繰り返し状態を確認するポーリング処理によるCPU負荷を軽減し、システムリソースを有効活用するために使用されます。

第一引数$multi_handleには、監視対象となるマルチcURLハンドル(curl_multi_initで初期化されたオブジェクト)を指定します。このハンドルに含まれるいずれかのcURLリクエストで、データの読み書きが可能になるのを待ちます。

第二引数$timeoutは、待機する最大時間を秒単位で浮動小数点数として指定します。この時間を過ぎると、データが利用可能になっていなくても関数は処理を中断し、次の処理へ進みます。デフォルト値は1.0秒です。

戻り値は整数で、データ転送の準備ができたハンドルの数を返します。もしエラーが発生した場合は-1、指定されたタイムアウト時間内に何も準備ができなかった場合は0を返します。

サンプルコードでは、do-whileループの中でcurl_multi_execと組み合わせて利用されています。curl_multi_execがまだ実行中のリクエストがあることを示した場合、curl_multi_selectを呼び出すことで、データが来るまでCPUを消費せずに待機します。これにより、全ての並行リクエストが効率的に完了するまで処理が進められ、取得したデータが最終的に返されます。処理完了後、各リクエストの結果が収集され、マルチcURLハンドルと個別のcURLハンドルは適切にクローズされます。

このサンプルコードは、複数のHTTPリクエストを並行して効率的に処理する方法を示しています。curl_multi_select関数は、データが利用可能になるまでプログラムの実行を一時停止させ、CPUの無駄な消費を防ぐ非常に重要な役割を担っています。引数で指定するタイムアウト値は、最大で待機する時間を秒単位で設定するため、適切な値を検討することが大切です。

初心者の方は、curl_multi_initcurl_initで作成したリソースが、処理終了時に必ずcurl_multi_closecurl_closeで解放されているかを確認してください。これを怠るとメモリリークの原因となります。また、各curl_関数の戻り値やエラーコードを常にチェックし、エラー発生時の適切な処理(エラーハンドリング)を丁寧に行うことが、安全で堅牢なコードを書く上で不可欠です。

【PHP8.x】curl_multi_select関数の使い方 | いっしー@Webエンジニア