はじめに
並行処理に特化しているとされるGoとウェブアプリの開発によく使われるPHPで並行処理の実現方法がどう違うのかまとめる。
GoとPHPの並行処理における比較
比較項目 | Go | PHP |
---|---|---|
基本設計 | 並行処理が言語レベルで組み込まれている | 並行処理は追加の拡張やライブラリを使う必要がある。 |
プロセス | 各リクエストは同じプロセスで処理される | 各リクエストは違うプロセスで処理される。 |
軽量性 | ゴルーチンは軽量で数千~数百万の並行タスクを処理可能。 | マルチプロセスはリソースを多く消費し、スレッドベースは限定的。 |
実装の簡潔さ | シンプルな構文で並行処理を記述可能 | マルチプロセスやイベントループベースで複雑なコードになる場合が多い。 |
スケジューリング | Goランタイムが自動でゴルーチンをスケジュール | プロセスやスレッドのスケジューリングはOS任せ。 |
主な用途 | 並行処理が頻繁に必要で、高い性能を求めるアプリケーション(例:リアルタイム通信、マイクロサービス) | ウェブアプリケーションの開発。並行処理は補助的な役割で、イベントループライブラリを利用して解決可能。 |
Goの並行処理の例
Goでは軽量スレッド(ゴルーチン)を使うため、簡潔なコードで高性能な並行処理が可能。
package main
import (
"fmt"
"time"
)
func task(name string) {
for i := 1; i <= 5; i++ {
fmt.Println(name, i)
time.Sleep(500 * time.Millisecond)
}
}
func main() {
go task("Task A")
go task("Task B")
task("Main Task")
}
Goで平行処理を実現する際の注意点
複数のgoroutineから同じメモリ(変数)へ同時にアクセスする(そのうち1つは書き込みである)ことで、予期しないエラーの原因になる。この状態のことをRace Conditionという。
以下コードはnet/httpを使用したHTTPサーバで各リクエストがメモリ空間を共有することを確認する例。
リクエストハンドラ内の変数cは全てのリクエストにおいて同じメモリ上のデータを指す。 したがって、アクセスのたびに変数cがインクリメントされる。
複数のリクエストが同時に発生した場合、変数cへ同時アクセスが発生し、Race Conditionになり、予期しないエラーが発生する可能性がある。
package main
import (
"fmt"
"log"
"net/http"
)
var c int
func main() {
http.HandleFunc("/count", func(w http.ResponseWriter, r *http.Request) {
// リクエスト毎に別のgoroutineで実行される
// 複数のリクエストによって同時に書き込みが発生する可能があり、cが意図しない状態の場合がある
c++
fmt.Fprintf(w, "count: %d, address: %v", c, &c)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
PHPの並行処理の例
PHPは主に同期的な動作を想定して設計されていて、並行処理を実現するためには追加の拡張や工夫が必要。
マルチプロセス
pcntl_fork() を使用してプロセスをフォーク。
PHPはプロセスベースの動作が一般的で、スレッドを直接扱うことは少ない。
<?php
function task($name) {
for ($i = 1; $i <= 5; $i++) {
echo "$name $i\n";
sleep(1);
}
}
$pid = pcntl_fork();
if ($pid == -1) {
die("Fork failed");
} elseif ($pid == 0) {
// 子プロセス
task("Child Process");
} else {
// 親プロセス
task("Parent Process");
pcntl_wait($status); // 子プロセスの終了を待機
}
非同期ライブラリ
・非同期I/O操作(例: HTTPリクエスト、ファイル操作)に適している。
・プロセスやスレッドを生成せずに、シングルスレッド内で複数のタスクを処理。
・パフォーマンスが高く、Webサーバーのバックエンドで利用されることが多い。
<?php
require 'vendor/autoload.php';
$loop = React\EventLoop\Factory::create();
$loop->addTimer(1, function () {
echo "Task 1 complete\n";
});
$loop->addTimer(2, function () {
echo "Task 2 complete\n";
});
$loop->run();