タイトル: 待機・処理の遅延
SEOタイトル: PHP sleep/usleep 待機処理完全ガイド
| この記事の要点 |
|
基本: sleep / usleep / time_nanosleep
用途別の使い分け
| 用途 | 推奨関数 | 備考 |
|---|---|---|
| 1 秒以上の単純な待ち | sleep() | 最もシンプル |
| ミリ秒単位の精密待機 | usleep() | API のレート制御等 |
| シグナル割り込みを検知したい | time_nanosleep() | 残時間を返す |
| 絶対時刻まで待つ | time_sleep_until($ts) | UNIX タイム指定 |
| マイクロ秒精度のタイマー測定 | hrtime(true) 差分 | sleep ではなく計測用 |
Web リクエスト処理中の sleep は危険
PHP-FPM はワーカープロセスでリクエストを処理しているため、sleep(10) を入れるとそのワーカーが 10 秒間ブロックします。同時実行数 50 に対し sleep 入りエンドポイントへ 100 リクエスト来れば、50 件が 504 Gateway Timeout 等で詰まります。
// ❌ アンチパターン: Web 経由の処理で sleep を使う
public function send(Request $request)
{
Mail::to($request->email)->send(new Welcome());
sleep(5); // ←ワーカーが 5 秒ブロック!
Mail::to($request->email)->send(new FollowUp());
return response()->json(['ok' => true]);
}
// ✅ 正解: ジョブキューに非同期で投げ、遅延配信
public function send(Request $request)
{
Mail::to($request->email)->send(new Welcome());
SendFollowUpJob::dispatch($request->email)
->delay(now()->addMinutes(5)); // 5 分後に実行
return response()->json(['ok' => true]);
}
Laravel Queue での遅延実行
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class SendReminderJob implements ShouldQueue
{
use Dispatchable, Queueable;
public function __construct(public int $userId) {}
public function handle(): void
{
// 実処理
}
}
// dispatch + delay
SendReminderJob::dispatch($user->id)
->delay(now()->addHours(24));
// dispatchAfterResponse: レスポンス送信後に同プロセス内で実行
SendReminderJob::dispatchAfterResponse($user->id);
// onQueue で別キュー
SendReminderJob::dispatch($user->id)
->delay(now()->addMinutes(10))
->onQueue('reminders');
Laravel Queue は database / redis / sqs 等のバックエンドを使い、php artisan queue:work ワーカーが時刻を見て実行します。Web リクエストはすぐにレスポンスを返せます。
cron との使い分け
| 処理時間 | 推奨 |
|---|---|
| ~100ms(API レート制御) | usleep() 直接 |
| 1秒~10秒(ポーリング待ち) | CLI スクリプトでの sleep() |
| 1 分以上 | cron / Laravel Scheduler |
| 特定日時に 1 回だけ | Queue の delay |
| 毎日決まった時刻 | cron / Scheduler |
HTTP リトライでの指数バックオフ
API がエラーを返したときに即リトライすると相手サーバを追い込みます。指数バックオフ + ジッターが定石:
function callWithBackoff(callable $fn, int $maxAttempts = 5)
{
for ($attempt = 0; $attempt < $maxAttempts; $attempt++) {
try {
return $fn();
} catch (\Throwable $e) {
if ($attempt === $maxAttempts - 1) throw $e;
// 2^n * 100ms + 0-100ms のランダムジッター
$baseMs = (1 << $attempt) * 100;
$jitter = random_int(0, 100);
usleep(($baseMs + $jitter) * 1000);
}
}
}
// 利用
$response = callWithBackoff(fn() => Http::get('https://api.example.com'));
// Laravel 標準の retry ヘルパも便利
$response = retry(5, function () {
return Http::throw()->get('https://api.example.com');
}, 100); // 100ms 起点で指数バックオフ
Laravel HTTP クライアントのリトライ
use Illuminate\Support\Facades\Http;
// 3 回まで、100ms 間隔
$response = Http::retry(3, 100)->get('https://api.example.com');
// バックオフ関数
$response = Http::retry(5, function ($attempt, $exception) {
return $attempt * 100; // 100ms, 200ms, 300ms, 400ms, 500ms
})->get('https://api.example.com');
// 特定例外のみリトライ
$response = Http::retry(3, 100, function ($exception, $request) {
return $exception instanceof ConnectionException;
})->get('https://api.example.com');
シグナル割り込みを処理する
長時間 sleep 中に Ctrl+C 等が来たら、残時間を取って正しく停止できます:
$result = time_nanosleep(10, 0); // 10 秒待つ
if (is_array($result)) {
// 中断された場合 ['seconds' => x, 'nanoseconds' => y] が返る
echo "中断されました。残り {$result['seconds']} 秒\n";
}
// sleep の場合
$remaining = sleep(10);
if ($remaining > 0) {
echo "中断されました。残り $remaining 秒\n";
}
FAQ
Q: usleep の最大値は?
A: 公式には 1,000,000 マイクロ秒(1 秒)未満が推奨。それ以上は sleep() や time_nanosleep() を使ってください。
Q: CLI コマンドでの sleep は問題ない?
A: 問題なし。CLI は 1 プロセスで動くため、Web ワーカーをブロックする問題は起きません。
Q: set_time_limit(0) と sleep を組み合わせて長時間処理したい
A: CLI なら可。Web リクエストで使うと PHP-FPM や Nginx のタイムアウト設定にもひっかかるので、Queue / cron に切り出すべきです。