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

curl_multi_select関数の使い方について、初心者にもわかりやすく解説します。

作成日: 更新日:

基本的な使い方

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

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

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

構文(syntax)

1<?php
2$multi_handle = curl_multi_init();
3$select_result = curl_multi_select($multi_handle, 0.5);
4curl_multi_close($multi_handle);
5?>

引数(parameters)

CurlMultiHandle $multi_handle, float $timeout = 1.0

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

戻り値(return)

int

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

サンプルコード

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

1<?php
2
3/**
4 * 複数のURLからデータを並行して取得します。
5 * curl_multi_select 関数を使用して、データが利用可能になるまで効率的に待機します。
6 *
7 * @param array $urls キーとURLのペアの配列 (例: ['task1' => 'http://example.com/api/data1'])
8 * @return array 各URLの取得結果を含む連想配列
9 */
10function fetch_urls_concurrently(array $urls): array
11{
12    // マルチ cURL ハンドルを初期化します。
13    // これにより、複数の cURL リクエストを同時に管理できるようになります。
14    $multi_handle = curl_multi_init();
15    if ($multi_handle === false) {
16        // 初期化に失敗した場合、エラーメッセージを出力し空の配列を返します。
17        echo "エラー: curl_multi_init の初期化に失敗しました。\n";
18        return [];
19    }
20
21    $curl_handles = []; // 各URLに対応するcURLハンドルの配列を格納
22    $results = [];       // 各URLの取得結果を格納する配列
23
24    // 各URLに対して個別の cURL ハンドルを作成し、設定します。
25    foreach ($urls as $id => $url) {
26        $ch = curl_init();
27        if ($ch === false) {
28            echo "エラー: URL '{$url}' の cURL ハンドルの初期化に失敗しました。\n";
29            continue; // 次のURLへ進みます
30        }
31        curl_setopt($ch, CURLOPT_URL, $url);             // リクエストするURLを設定
32        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  // 取得したデータを文字列として返すように設定
33        // 必要に応じて、他のcURLオプションもここで設定できます。
34        // 例: curl_setopt($ch, CURLOPT_TIMEOUT, 10); // タイムアウトを10秒に設定
35
36        // 作成したcURLハンドルをマルチ cURL ハンドルに追加します。
37        // これで、このハンドルが並行処理の対象となります。
38        curl_multi_add_handle($multi_handle, $ch);
39        $curl_handles[$id] = $ch; // 後で結果を取得するためにハンドルを保存
40    }
41
42    $running_handles = null; // 現在アクティブなcURLハンドルの数を追跡するための変数
43
44    // 全てのcURL転送が完了するまでループします。
45    do {
46        // curl_multi_exec は、マルチハンドル内の全ての cURL ハンドルの転送を実行します。
47        // 第2引数 ($running_handles) には、まだ転送中のハンドルの数が設定されます。
48        $status = curl_multi_exec($multi_handle, $running_handles);
49
50        // curl_multi_exec がエラーを返した場合
51        if ($status !== CURLM_OK) {
52            echo "エラー: curl_multi_exec の実行中にエラーが発生しました: " . curl_multi_strerror($status) . "\n";
53            break;
54        }
55
56        // まだ転送中のハンドルがある場合 (つまり、$running_handles が 0 より大きい場合)
57        if ($running_handles > 0) {
58            // curl_multi_select は、データが利用可能になるまでブロック (待機) します。
59            // これにより、CPU使用率を抑えつつ、効率的に待機できます。
60            // タイムアウト値 (ここでは 1.0 秒) は、最大で待機する時間を指定します。
61            // 戻り値は準備ができたハンドルの数、-1 はエラー、0 はタイムアウトを示します。
62            $select_result = curl_multi_select($multi_handle, 1.0); // 最大1秒待機
63            if ($select_result === -1) {
64                echo "エラー: curl_multi_select の実行中にエラーが発生しました。\n";
65                break; // エラーが発生したためループを抜けます
66            } elseif ($select_result === 0) {
67                // タイムアウトしましたが、まだデータは利用可能になっていない場合。
68                // この場合でも、次の curl_multi_exec を呼び出す必要があります。
69                // 必要であればここにログなどを出力できますが、通常は処理を続行します。
70            }
71        }
72    } while ($running_handles > 0); // アクティブなハンドルがなくなるまでループを続行
73
74    // 全ての転送が完了した後、各 cURL ハンドルの結果を取得します。
75    foreach ($curl_handles as $id => $ch) {
76        $error_code = curl_errno($ch); // cURLハンドルのエラーコードを取得
77
78        if ($error_code === 0) {
79            // エラーがない場合、取得したコンテンツとHTTPステータスコードを取得します。
80            $content = curl_multi_getcontent($ch);
81            $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
82            $results[$id] = [
83                'status' => 'success',
84                'http_code' => $http_code,
85                'content' => $content,
86            ];
87        } else {
88            // エラーがある場合、エラーメッセージとコードを記録します。
89            $error_message = curl_error($ch);
90            $results[$id] = [
91                'status' => 'error',
92                'message' => $error_message,
93                'error_code' => $error_code,
94            ];
95        }
96
97        // 各 cURL ハンドルをマルチハンドルから削除し、閉じます。
98        // これにより、リソースが解放されます。
99        curl_multi_remove_handle($multi_handle, $ch);
100        curl_close($ch);
101    }
102
103    // 最後に、マルチ cURL ハンドルを閉じます。
104    curl_multi_close($multi_handle);
105
106    return $results;
107}
108
109// --- サンプル使用方法 ---
110
111// 複数のURLを定義します。テスト用にダミーのAPIエンドポイントを含めます。
112$urls_to_fetch = [
113    'user_data_1' => 'https://jsonplaceholder.typicode.com/todos/1',
114    'user_data_2' => 'https://jsonplaceholder.typicode.com/todos/2',
115    'post_data_1' => 'https://jsonplaceholder.typicode.com/posts/1',
116    'invalid_url' => 'https://example.com/non_existent_page_12345', // 存在しないURLの例
117];
118
119echo "複数のURLからデータを非同期で取得を開始します...\n";
120$start_time = microtime(true); // 処理開始時刻を記録
121
122$fetched_data = fetch_urls_concurrently($urls_to_fetch);
123
124$end_time = microtime(true); // 処理終了時刻を記録
125echo "データの取得が完了しました。処理時間: " . round($end_time - $start_time, 2) . "秒\n\n";
126
127// 取得したデータを表示します。
128foreach ($fetched_data as $task_id => $data) {
129    echo "--- タスクID: {$task_id} ---\n";
130    if ($data['status'] === 'success') {
131        echo "ステータス: 成功\n";
132        echo "HTTPコード: " . $data['http_code'] . "\n";
133        // コンテンツが長い場合があるので、先頭100文字だけ表示します。
134        echo "コンテンツ (先頭100文字): " . substr($data['content'], 0, 100) . "...\n";
135    } else {
136        echo "ステータス: エラー\n";
137        echo "エラーメッセージ: " . $data['message'] . " (コード: " . $data['error_code'] . ")\n";
138    }
139    echo "\n";
140}
141
142?>

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_関数の戻り値やエラーコードを常にチェックし、エラー発生時の適切な処理(エラーハンドリング)を丁寧に行うことが、安全で堅牢なコードを書く上で不可欠です。

関連コンテンツ