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