PHPのマルチスレッド化とは、複数の処理を同時並行で実行し、処理時間の短縮や待ち時間の有効活用を目指す考え方です。
ただし、PHPはもともと、JavaやGoのようにアプリケーションコード内でスレッドを積極的に扱う前提の言語ではありません。
一般的なPHPのWebアプリケーションでは、1つのリクエストを1つの実行単位として処理し、複数リクエストの並列処理はPHP-FPMやWebサーバー側のワーカーによって行われることが多いです。
そのため、PHPで処理を速くしたい場合でも、すぐにマルチスレッド化を考えるのではなく、処理の内容に応じて、キュー処理、マルチプロセス化、非同期処理、外部ワーカーなどを使い分けることが重要です。
PHPではマルチスレッド化より並列処理の設計が重要
PHPで「マルチスレッド化」と言う場合、実際にはいくつかの異なる技術が混同されやすいです。
厳密なマルチスレッドだけでなく、マルチプロセス、キュー処理、非同期I/O、バックグラウンド処理なども、広い意味では「複数の処理を並行して進める方法」として扱われます。
マルチスレッド
マルチスレッドは、1つのプロセス内で複数のスレッドを動かす方法です。
PHPで本格的なスレッド処理を行う場合は、主にparallel拡張が候補になります。
ただし、ZTS有効のPHPビルドが必要で、一般的なレンタルサーバーや通常のPHP-FPM環境では導入しにくい場合があります。
マルチプロセス
マルチプロセスは、複数のPHPプロセスを起動して処理を分担する方法です。
PHPではpcntl_fork()を使った方法が代表的です。
これはマルチスレッドではありませんが、CLIバッチや大量データ処理では実用的な選択肢になります。
キュー処理
キュー処理は、重い処理をジョブとして登録し、バックグラウンドワーカーに処理させる方法です。
Webアプリケーションでは、最も実用的な並列処理の方法です。
メール送信、PDF生成、画像変換、外部API連携、CSV処理などをキュー化することで、ユーザーへのレスポンスを速くできます。
非同期処理
非同期処理は、外部API通信やファイルI/Oなどの待ち時間を有効活用する方法です。
マルチスレッドのように複数CPUコアで同時に計算する仕組みとは異なりますが、HTTPリクエストやDBアクセスなど、待ち時間が多い処理では効果を発揮します。
PHPが標準でマルチスレッド向きではない理由
PHPはWeb開発に広く使われている言語ですが、標準的な実行スタイルは「リクエスト単位の処理」です。
たとえば、Webサイトにアクセスがあった場合、PHPはそのリクエストに対して処理を行い、HTMLやJSONなどのレスポンスを返します。
この処理が終わると、そのリクエストに関するPHPの実行も終了するのが一般的です。
Webサーバー側で並列処理されることが多い
PHPアプリケーションでは、アプリケーションコード自体をマルチスレッド化しなくても、PHP-FPMやWebサーバーが複数リクエストを並列に処理します。
そのため、通常のWebページ表示を高速化したい場合、PHPコード内でスレッドを作るよりも、以下のような改善の方が効果的です。
- SQLの最適化
- N+1問題の解消
- ページキャッシュの導入
- OPcacheの有効化
- CDNの利用
- 画像の軽量化
- 不要なプラグインや処理の削減
- PHP-FPMのワーカー数調整
特にWordPressや一般的なCMSでは、PHPのマルチスレッド化よりも、キャッシュやサーバー設定の見直しの方が現実的です。
スレッドセーフな環境が必要になる
PHPでスレッドを扱う場合、ZTS、つまりZend Thread Safetyが有効なPHPビルドが必要になることがあります。
たとえば、parallel拡張を使うにはZTS有効のPHPが必要です。
ZTSは後から設定ファイルで簡単に有効化できるものではなく、PHPのビルド時に決まるものです。
そのため、一般的なレンタルサーバーや共有サーバーでは、PHPのスレッド処理を自由に導入できないことがあります。
ライブラリや拡張機能の扱いに注意が必要
PHPのすべての拡張機能やライブラリが、スレッド環境で安全に使えるとは限りません。
DB接続、ファイルハンドル、外部APIクライアント、グローバル変数、共有オブジェクトなどをスレッド間で安易に共有すると、予期しないエラーやデータ競合が発生する可能性があります。
PHPで並列処理を行う場合は、「処理を共有する」のではなく、「処理単位を分けて、それぞれ独立して実行する」設計が基本になります。
PHPで使える主な並列処理の方法
PHPで複数処理を並行して実行する方法には、いくつかの選択肢があります。
それぞれに向いている用途や注意点があるため、目的に合わせて選ぶことが大切です。
parallel拡張を使う方法
PHPで本格的にスレッドベースの並列処理を行いたい場合、現在の代表的な選択肢はparallel拡張です。
parallelでは、parallel\Runtimeを使って別の実行環境を作り、その中で処理を実行できます。
実行した処理の結果は、parallel\Futureを通じて後から受け取ることができます。
parallel拡張の基本的な考え方
parallelでは、メインの処理とは別にRuntimeを作成し、そのRuntime上で別の処理を実行します。
たとえば、重い計算処理や時間のかかる処理を別Runtimeに渡し、メイン処理側では別の作業を進めることができます。
<?php
$runtime = new parallel\Runtime();
$future = $runtime->run(function () {
sleep(2);
return 100 + 200;
});
echo "別の処理を実行中...\n";
$result = $future->value();
echo "結果: " . $result . "\n";
この例では、$runtime->run()で別Runtimeに処理を渡し、$future->value()で結果を受け取っています。
複数の処理を並列実行する例
複数のRuntimeを作成すれば、複数の処理を並列に実行できます。
<?php
$runtimes = [];
$futures = [];
for ($i = 1; $i <= 4; $i++) {
$runtimes[$i] = new parallel\Runtime();
$futures[$i] = $runtimes[$i]->run(function ($number) {
sleep(2);
return $number * 10;
}, [$i]);
}
foreach ($futures as $i => $future) {
echo "Task {$i}: " . $future->value() . PHP_EOL;
}
sleep()のような待ち時間中心の処理であれば、並列化によって合計時間を短縮しやすくなります。
ただし、CPUを多く使う処理では、CPUコア数、メモリ、スレッド生成コスト、外部サービスやDBの制限によって効果が変わります。
並列化すれば必ず速くなるわけではありません。
parallel拡張のメリット
parallelのメリットは、PHPの中でスレッドベースの並列処理を扱えることです。
処理の戻り値をFutureで受け取れるため、非同期タスクの結果管理がしやすい点も特徴です。
また、parallel\Channelを使えば、タスク間でデータをやり取りすることもできます。
parallel拡張の注意点
parallelは便利ですが、導入ハードルは高めです。
まず、ZTS有効のPHPが必要です。
一般的なPHP-FPM環境やレンタルサーバーでは、そのまま使えないことがあります。
また、Runtime::run()に渡すクロージャには制約があります。
通常のPHPコードを何でもそのまま別スレッドに渡せるわけではありません。
参照渡し、参照のuse、一部の内部オブジェクト、リソース、DB接続、ファイルハンドルなどは注意が必要です。
実務では、親側で作成したDB接続や外部サービスのクライアントをそのまま渡すのではなく、各Runtime内で必要な接続を作り直す設計にする方が安全です。
pthreads拡張を使う方法
以前は、PHPのマルチスレッド化というとpthreads拡張が使われることがありました。
しかし、現在の新規開発では基本的におすすめできません。
pthreadsは現在では選びにくい
pthreadsは過去にPHPでスレッド処理を行うために使われていた拡張ですが、現在はメンテナンス状況に不安があります。
また、Webサーバー環境では使えず、CLIアプリケーション向けの拡張でした。
そのため、通常のWebアプリケーションで利用する選択肢にはなりにくいです。
新規開発ではparallelを検討する
現在、PHPでスレッドベースの並列処理を検討するなら、pthreadsではなくparallelを検討する方が自然です。
ただし、parallelも導入環境を選ぶため、Webアプリケーションではキュー処理やマルチプロセス化を優先した方がよいケースが多いです。
pcntl_forkによるマルチプロセス化
PHPで実務的に使われる並列処理の方法として、pcntl_fork()によるマルチプロセス化があります。
これはマルチスレッドではありません。
親プロセスから子プロセスを作り、複数のPHPプロセスで処理を分担する方法です。
pcntl_forkの基本例
<?php
$pid = pcntl_fork();
if ($pid == -1) {
die("forkに失敗しました\n");
}
if ($pid) {
echo "親プロセスです。子プロセスID: {$pid}\n";
pcntl_wait($status);
echo "子プロセスが終了しました\n";
} else {
echo "子プロセスです\n";
sleep(2);
echo "子プロセスの処理が完了しました\n";
exit;
}
この例では、親プロセスから子プロセスを作成し、子プロセス側で別の処理を実行しています。
複数プロセスで処理する例
<?php
$tasks = range(1, 10);
$children = [];
$maxProcesses = 3;
foreach ($tasks as $task) {
while (count($children) >= $maxProcesses) {
$endedPid = pcntl_wait($status);
unset($children[$endedPid]);
}
$pid = pcntl_fork();
if ($pid == -1) {
die("forkに失敗しました\n");
}
if ($pid) {
$children[$pid] = true;
} else {
echo "タスク {$task} を処理中\n";
sleep(2);
echo "タスク {$task} が完了\n";
exit(0);
}
}
while (count($children) > 0) {
$endedPid = pcntl_wait($status);
unset($children[$endedPid]);
}
この例では、最大3プロセスまで同時に実行するように制御しています。
並列処理では、同時実行数の制限が非常に重要です。
大量のタスクを一気に子プロセス化すると、CPU、メモリ、DB接続数、外部APIアクセス数が急増し、サーバー全体が不安定になる可能性があります。
pcntl_forkが向いている処理
pcntl_fork()は、LinuxなどUnix系環境のCLIバッチで使うのに向いています。
具体的には、以下のような処理で検討できます。
- 大量CSVの分割処理
- 画像変換
- ログ解析
- 大量ファイルの処理
- APIデータの一括取得
- クローラー
- バッチ処理の高速化
pcntl_forkの注意点
pcntl_fork()はWebリクエスト中に安易に使うものではありません。
通常のWebアクセス処理の中で子プロセスを作ると、プロセス管理、タイムアウト、ログ、DB接続、リソース解放、エラー処理が複雑になります。
また、親子プロセス間でメモリは直接共有されません。
処理結果を集約するには、DB、ファイル、Redis、メッセージキュー、標準出力、ソケットなどを使う必要があります。
DB接続についても注意が必要です。
fork前に作ったDB接続を子プロセスでそのまま使うと、予期しない問題が起きることがあります。
子プロセス側で接続を作り直す設計にする方が安全です。
外部プロセスとしてPHPコマンドを実行する方法
PHPから別のPHPコマンドを起動し、複数プロセスで処理を分担する方法もあります。
たとえば、LaravelのArtisanコマンドやSymfony Consoleコマンドを複数起動して、処理対象を分割する方法です。
外部プロセス化の考え方
たとえば、CSVファイルを複数に分割し、それぞれを別コマンドで処理します。
php import.php part_001.csv
php import.php part_002.csv
php import.php part_003.csv
このように処理単位を分ければ、OS側で複数プロセスとして並列実行できます。
execを使う場合の注意点
PHPにはexec()などの外部コマンド実行関数があります。
ただし、安易に使うと以下のような問題が発生します。
- コマンドインジェクションのリスクがある
- エラー検知が難しい
- ログ管理が雑になりやすい
- 実行中プロセスの監視が難しい
- 多重起動制御が必要になる
- サーバー環境によっては制限される
- Webサーバーユーザー権限で実行される
そのため、実務ではexec()を直接多用するより、Symfony Process、Laravel Queue、Supervisor、systemd、cron、Docker、Kubernetes Jobsなどで管理する方が安全です。
キュー処理によるバックグラウンド化
Webアプリケーションで重い処理を分離したい場合、最も実用的な選択肢はキュー処理です。
キュー処理は厳密にはマルチスレッドではありませんが、複数のワーカーを起動することでジョブを並列に処理できます。
キュー処理の基本的な仕組み
キュー処理では、時間のかかる処理をその場で実行せず、ジョブとしてキューに登録します。
その後、バックグラウンドで動いているワーカーがジョブを取り出して処理します。
たとえば、ユーザー登録時に以下の処理があるとします。
- DBにユーザー情報を保存する
- ユーザーへメールを送る
- 管理者へ通知する
- 外部CRMに連携する
- Slackに通知する
これらをすべてWebリクエスト中に実行すると、ユーザーの待ち時間が長くなります。
そこで、メール送信や外部API連携などをキューに入れ、バックグラウンドで処理させます。
これにより、ユーザーには素早くレスポンスを返せます。
Laravel Queueの例
Laravelでは、以下のようにジョブをキューへ投入できます。
SendWelcomeEmail::dispatch($user);
SyncUserToCrm::dispatch($user);
NotifySlack::dispatch($user);
バックグラウンド側では、キューワーカーを起動します。
php artisan queue:work
複数のワーカーを起動すれば、複数のジョブを並列に処理できます。
php artisan queue:work
php artisan queue:work
php artisan queue:work
キュー処理が向いているケース
キュー処理は、以下のような処理に向いています。
- メール送信
- PDF生成
- 画像変換
- 動画変換
- 外部API連携
- CSVインポート
- CSVエクスポート
- 通知処理
- レポート生成
- クローラー
- 予約投稿
- データ同期
Webアプリケーションでは、重い処理をキュー化することで、ユーザー体験を改善しながらサーバー負荷を調整しやすくなります。
キュー処理の注意点
キュー処理でも、ワーカー数を増やせば必ず安全に速くなるわけではありません。
ワーカー数を増やすと、DB接続数、Redisへのアクセス、外部APIへのリクエスト数、CPU使用率、メモリ使用量が増えます。
また、同じデータを複数ジョブが同時に更新する場合は、排他制御が必要です。
実務では、以下のような設計が重要です。
- ジョブのタイムアウト設定
- リトライ回数の設定
- 失敗ジョブの管理
- 同時実行数の制御
- Supervisorなどによるワーカー監視
- ログ管理
- 重複実行の防止
- DBロックやRedisロックの活用
非同期処理を使う方法
PHPでは、ReactPHP、Amp、Swoole、OpenSwooleなどを使って非同期処理を実装することもできます。
非同期処理はマルチスレッドとは異なります。
複数の処理をCPU上で同時に実行するというより、I/O待ちの時間を有効活用する仕組みです。
非同期処理とマルチスレッドの違い
非同期処理は「同時に待てる」仕組みです。
たとえば、外部APIに10件のリクエストを送る場合、同期処理では1件目のレスポンスを待ってから2件目を送ります。
一方、非同期処理では複数のリクエストを同時に投げ、返ってきたものから処理できます。
これにより、外部APIやネットワーク通信の待ち時間を短縮できます。
非同期処理が向いている処理
非同期処理は、以下のようなI/O待ちが多い処理に向いています。
- 外部APIアクセス
- HTTPリクエスト
- DB問い合わせ
- ファイルI/O
- ソケット通信
- WebSocket
- チャットサーバー
- リアルタイム通知
CPU負荷が高い処理には向かない場合がある
非同期処理は、待ち時間の多い処理には効果的です。
しかし、画像変換、大量計算、暗号化、動画処理など、CPUを使い続ける処理では、非同期処理だけでは十分に速くならない場合があります。
CPU負荷が高い処理では、マルチプロセス、複数ワーカー、別サーバー、コンテナ分散などを検討する方が適しています。
Fiberを使う方法
PHP 8.1以降にはFiberがあります。
Fiberは、処理を途中で中断し、後から再開できる仕組みです。
ただし、Fiber自体はマルチスレッドではありません。
Fiberの基本例
<?php
$fiber = new Fiber(function () {
echo "処理A\n";
Fiber::suspend("一時停止しました");
echo "処理B\n";
return "完了";
});
$value = $fiber->start();
echo $value . PHP_EOL;
$result = $fiber->resume();
echo $result . PHP_EOL;
この例では、Fiber::suspend()で処理を一時停止し、後からresume()で再開しています。
Fiberは直接的な並列処理ではない
Fiberは、複数CPUコアで同時に処理を走らせる仕組みではありません。
どちらかというと、非同期処理ライブラリやイベントループの内部で使われる低レベルな仕組みです。
そのため、Fiberを使えばPHPが自動的にマルチスレッド化されるわけではありません。
PHPのマルチスレッド化・並列処理が向いているケース
PHPで並列処理を検討する価値があるのは、処理に時間がかかり、なおかつ処理単位を分割しやすいケースです。
大量の外部API取得
外部APIを大量に呼び出す処理では、並列処理や非同期処理が有効です。
たとえば、100件の商品データを外部APIから取得する場合、1件ずつ順番に取得すると時間がかかります。
複数件を同時に取得できれば、全体の処理時間を短縮できる可能性があります。
ただし、外部APIにはレート制限があります。
並列数を増やしすぎると、429 Too Many Requestsなどのエラーが発生することがあります。
画像処理
大量の画像をリサイズする、サムネイルを作成する、WebPに変換する、といった処理も並列化の対象になります。
ただし、画像処理はCPUやメモリを多く使います。
並列数を上げすぎると、サーバー全体の負荷が高くなるため注意が必要です。
CSVインポート
数十万行、数百万行のCSVを処理する場合、ファイルを分割して複数プロセスや複数ワーカーで処理できます。
ただし、DBへの書き込みがボトルネックになることも多いです。
単純に並列数を増やすだけでなく、バルクインサート、インデックス設計、トランザクション、重複チェック、ロック制御も見直す必要があります。
クローラー・スクレイピング
複数ページを取得するクローラーでは、並列処理の効果が出やすいです。
ただし、対象サイトへの負荷、robots.txt、アクセス間隔、利用規約、IPブロック、法的リスクには注意が必要です。
メール送信
大量メール送信も並列化できます。
ただし、SMTPサーバーやメール配信サービスには送信数の制限があります。
実務では、PHPで直接大量送信するよりも、キュー処理とメール配信サービスを組み合わせる方が安全です。
PHPのマルチスレッド化・並列処理が向いていないケース
並列化は便利ですが、どのような処理にも向いているわけではありません。
むしろ、並列化によって複雑さや負荷が増え、逆効果になるケースもあります。
通常のWebページ表示
通常のWebページ表示では、PHPコード内でマルチスレッド化する必要はほとんどありません。
WebサーバーやPHP-FPMがリクエスト単位で並列処理しているため、1リクエスト内でスレッドを作るよりも、以下の改善を優先すべきです。
- SQLの最適化
- キャッシュの導入
- 画像の軽量化
- CDNの利用
- PHP-FPMの設定見直し
- 不要な処理の削減
- 外部API呼び出しの削減
WordPressの通常運用
WordPressサイトで「PHPをマルチスレッド化したい」と考える場合、多くは方向性が違います。
WordPressでは、PHPのスレッド化よりも以下の改善が現実的です。
- ページキャッシュ
- オブジェクトキャッシュ
- OPcache
- CDN
- 画像圧縮
- 遅延読み込み
- 不要プラグインの削除
- データベース最適化
- PHP-FPMのチューニング
- サーバースペックの見直し
短時間で終わる処理
処理自体が軽い場合、並列化のオーバーヘッドの方が大きくなることがあります。
スレッドやプロセスの生成、タスク分割、通信、結果集約、エラー処理にはコストがかかります。
数ミリ秒から数十ミリ秒で終わる処理であれば、並列化しない方が効率的な場合もあります。
PHPで並列処理を行うメリット
PHPで適切に並列処理を導入すると、処理時間の短縮やユーザー体験の改善につながります。
処理時間を短縮できる
並列処理の大きなメリットは、全体の処理時間を短縮できる可能性があることです。
たとえば、1件あたり2秒かかるAPI取得を50件行う場合、直列処理では単純計算で100秒かかります。
これを10並列で処理できれば、理論上は大幅に短縮できる可能性があります。
ただし、実際にはAPI制限、通信速度、CPU、メモリ、DB、ネットワーク帯域などの影響を受けます。
待ち時間を有効活用できる
外部API、DB、ファイル読み込み、ネットワーク通信などでは、処理の待ち時間が発生します。
並列処理や非同期処理を使うことで、その待ち時間に別の処理を進められます。
大量データ処理に対応しやすくなる
CSV、ログ、商品データ、アクセス解析データなどを大量に処理する場合、処理単位を分割して並列実行することで、バッチ処理の時間を短縮できます。
夜間バッチが朝までに終わらない、管理画面でCSVインポートがタイムアウトする、といった問題の改善に役立ちます。
PHPで並列処理を行うデメリット
並列処理にはメリットだけでなく、デメリットもあります。
設計や運用が複雑になるため、導入前にリスクを理解しておく必要があります。
実装が複雑になる
並列処理では、直列処理にはない問題が発生します。
たとえば、以下のような要素を考える必要があります。
- 処理順序
- 排他制御
- データ競合
- ロック
- タイムアウト
- リトライ
- エラー集約
- ログ管理
- 子プロセスの監視
- メモリ使用量
- ゾンビプロセス対策
単純な処理であれば、並列化しない方が保守しやすい場合もあります。
デバッグが難しくなる
並列処理では、エラーが再現しにくくなります。
ある時は成功し、ある時は失敗するという問題が起きやすくなります。
そのため、ログにはタスクID、プロセスID、対象データID、開始時刻、終了時刻、処理時間、エラー内容、リトライ回数などを残すことが重要です。
サーバー負荷が増える
並列数を増やすと、CPU、メモリ、DB接続数、ネットワーク帯域、外部APIアクセス数が増えます。
「10並列で速くなったから100並列にする」といった単純な増やし方は危険です。
並列数は、サーバースペック、DB性能、外部APIの制限、キューの処理能力を見ながら慎重に調整する必要があります。
環境依存が増える
parallelやpcntlなどは、利用できる環境が限られます。
共用レンタルサーバーでは、必要な拡張機能が使えないこともあります。
VPS、クラウドサーバー、Docker、自社管理サーバーの方が導入しやすいです。
PHPで並列処理を設計するときのポイント
PHPで並列処理を行う場合は、単に処理を同時に実行するだけでなく、安全に運用できる設計にする必要があります。
処理単位を小さく分ける
並列化するには、処理を分割できる形にする必要があります。
たとえば、以下のように分けると処理しやすくなります。
- 商品IDごと
- ユーザーIDごと
- CSVの行範囲ごと
- 画像ファイルごと
- 日付ごと
- 店舗ごと
- カテゴリごと
1つの巨大な処理にすべてのロジックが詰まっていると、並列化しにくくなります。
同時実行数を制限する
並列処理では、同時実行数の制限が非常に重要です。
API取得は最大5並列、画像変換は最大2並列、DB書き込みは最大3並列など、処理の性質に応じて上限を決めます。
並列数を上げるほど速くなるとは限りません。
ボトルネックがDBや外部APIにある場合、並列数を増やすことで逆に遅くなることもあります。
処理状態を管理する
並列処理では、一部のタスクだけ失敗することがあります。
そのため、各タスクの状態を管理できるようにしておくと安全です。
たとえば、以下のようなステータスを持たせます。
pending
processing
completed
failed
retrying
このように管理すれば、失敗したタスクだけを再実行できます。
共有リソースを慎重に扱う
並列処理では、複数の処理が同じDBレコード、同じファイル、同じキャッシュキーを同時に更新する可能性があります。
このような場合、データ競合が発生します。
対策として、以下のような方法があります。
- DBトランザクション
- 行ロック
- ユニーク制約
- Redisロック
- 楽観的ロック
- ジョブの重複実行防止
- 処理対象の分割
基本的には、複数の処理が同じデータを同時に更新しない設計にするのが理想です。
リトライ設計を入れる
外部APIやネットワーク処理では、一時的な失敗が起きることがあります。
そのため、一定回数リトライできる設計にしておくと安定します。
ただし、短時間に何度もリトライすると、外部サービスに負荷をかけたり、レート制限に引っかかったりします。
リトライ間隔を徐々に広げる設計が望ましいです。
PHPで並列化する前に確認すべきこと
並列化は、遅い処理を自動的に速くする魔法の方法ではありません。
まずは、どこがボトルネックになっているかを確認する必要があります。
ボトルネックを特定する
並列化する前に、以下を確認します。
- 遅い原因はPHPの計算処理か
- DBクエリが遅いのか
- N+1問題が発生していないか
- 外部APIの待ち時間が長いのか
- ファイルI/Oが遅いのか
- CPU使用率は高いのか
- メモリは足りているのか
- キャッシュで解決できないか
- Webリクエストから切り離せないか
原因がSQLのN+1問題であれば、並列化しても根本解決になりません。
むしろDBへの同時アクセスが増えて、悪化する可能性もあります。
まずは基本的な高速化を行う
通常のWebサイトやWebアプリでは、並列処理より先に以下を見直すべきです。
- SQL最適化
- インデックス設計
- キャッシュ
- OPcache
- CDN
- 画像最適化
- 不要な外部API呼び出しの削減
- 重い処理のキュー化
- PHP-FPM設定
- サーバースペック
並列化は、これらの改善を行ったうえで、なお処理時間が問題になる場合に検討するのが現実的です。
実務での選び方
PHPで並列処理を行う場合は、目的に応じて適切な方法を選ぶ必要があります。
通常のWebサイトやCMSの場合
通常のコーポレートサイト、メディアサイト、WordPressサイト、LPなどでは、PHPのマルチスレッド化はほとんど不要です。
優先すべきなのは、キャッシュ、DB最適化、画像軽量化、CDN、サーバー設定の見直しです。
LaravelなどのWebアプリの場合
LaravelやSymfonyなどのWebアプリケーションでは、まずキュー処理を検討します。
メール送信、外部API連携、CSV処理、PDF生成、通知処理などはジョブ化し、複数ワーカーで処理するのが実務的です。
CLIバッチの場合
Linuxサーバー上でCLIバッチを動かす場合は、pcntl_fork()によるマルチプロセス化や、複数コマンドによる分散実行が候補になります。
大量データ処理では、対象データを分割し、同時実行数を制限することが重要です。
外部API待ちが多い場合
外部APIやHTTP通信の待ち時間が多い処理では、非同期処理が向いている場合があります。
複数のAPIリクエストを同時に投げ、返ってきたものから処理することで、待ち時間を短縮できます。
CPU負荷が高い場合
画像変換、大量計算、動画処理などCPU負荷が高い処理では、非同期処理だけでは不十分です。
この場合は、マルチプロセス、複数ワーカー、別サーバー、コンテナ分散などを検討します。
本格的なスレッド処理が必要な場合
どうしてもPHP内でスレッドベースの並列処理を行いたい場合は、parallel拡張を検討します。
ただし、ZTS有効のPHPが必要で、環境構築や運用の難易度が上がります。
一般的なWeb制作案件やCMS案件では、最初の選択肢にする必要はあまりありません。
PHPのマルチスレッド化に関するまとめ
PHPでも、マルチスレッドや並列処理は可能です。
ただし、一般的なWebアプリケーションでPHPコードを直接マルチスレッド化するのは、あまり標準的な方法ではありません。
現在、PHPで本格的なスレッド処理を行うならparallel拡張が候補になります。
ただし、ZTS有効のPHPが必要で、導入できる環境は限られます。
過去に使われていたpthreadsは、現在の新規開発では基本的に避けるべきです。
実務では、Webアプリならキュー処理、CLIバッチならpcntl_fork()によるマルチプロセス化、API待ちが多い処理なら非同期処理、大量データ処理なら複数ワーカーや外部プロセス化を検討するのが現実的です。
重要なのは、何でもマルチスレッド化することではありません。
PHPで処理を高速化したい場合は、まずボトルネックを特定し、SQL、キャッシュ、I/O、外部API、CPU、メモリのどこに問題があるのかを確認することが大切です。
そのうえで、処理の性質に合わせて、スレッド、プロセス、キュー、非同期処理を適切に使い分けることが、安定したPHPアプリケーション開発につながります。
以上、PHPのマルチスレッド化についてでした。
最後までお読みいただき、ありがとうございました。










