はじめに
PHPスクリプトの実行を一定時間一時停止したい場面はよくあります。APIのレート制限対策・リトライ処理の間隔確保・ポーリング処理・バッチ処理の負荷分散など、「少し待ってから次の処理へ」というニーズは非常に多様です。
sleep() はスクリプトの実行を指定した秒数だけ停止する関数です。シンプルですが、usleep()(マイクロ秒)・time_nanosleep()(ナノ秒)・time_sleep_until()(指定時刻まで)といった関連関数と組み合わせることで、精度の高い待機制御が実現できます。
関数の概要
| 項目 | 内容 |
|---|
| 関数名 | sleep() |
| 所属 | PHP 時間関数 |
| 導入バージョン | PHP 4以降 |
| PHP 8.x | 対応済み |
構文
sleep(int $seconds): int
パラメータ
| パラメータ | 型 | 説明 |
|---|
$seconds | int | 停止する秒数(0以上の整数) |
戻り値
| 戻り値 | 意味 |
|---|
0 | 正常に指定秒数だけ停止した |
| 正の整数 | シグナルによって中断された場合、残り秒数を返す |
⚠️ $seconds に負の値を渡すと ValueError が発生します(PHP 8.0以降)。
待機精度別の関連関数一覧
| 関数 | 精度 | 引数 | 用途 |
|---|
sleep(int $seconds) | 秒 | 秒数(int) | 秒単位の待機 |
usleep(int $microseconds) | マイクロ秒 | マイクロ秒(int) | ミリ秒・マイクロ秒単位の待機 |
time_nanosleep(int $sec, int $nanosec) | ナノ秒 | 秒 + ナノ秒 | ナノ秒精度の待機 |
time_sleep_until(float $timestamp) | マイクロ秒 | Unixタイムスタンプ | 指定時刻まで待機 |
換算表
1秒 = 1,000 ミリ秒 = 1,000,000 マイクロ秒 = 1,000,000,000 ナノ秒
sleep(1) → 1秒
usleep(1_000_000) → 1秒
usleep(500_000) → 0.5秒(500ミリ秒)
usleep(100_000) → 0.1秒(100ミリ秒)
usleep(1_000) → 0.001秒(1ミリ秒)
基本的な使い方
<?php
// 3秒間停止する
echo "処理開始\n";
sleep(3);
echo "3秒後\n";
// usleep で 500ミリ秒待機
usleep(500_000);
echo "500ms後\n";
// 指定時刻まで待機
$target = microtime(true) + 2.5; // 2.5秒後
time_sleep_until($target);
echo "2.5秒後\n";
実践的なクラスベースの使用例
例1:APIレート制限対応リトライクラス
<?php
/**
* APIレート制限に対応したリトライ処理クラス
* 指数バックオフ(Exponential Backoff)で待機時間を徐々に延ばす
*/
class ExponentialBackoffRetry
{
public function __construct(
private readonly int $maxAttempts = 5,
private readonly float $baseDelaySeconds = 1.0,
private readonly float $maxDelaySeconds = 60.0,
private readonly float $multiplier = 2.0,
private readonly float $jitterFactor = 0.1
) {}
/**
* コールバックをリトライ付きで実行する
*
* @param callable(): mixed $operation 実行する処理
* @param callable(\Throwable): bool $retryOn どの例外でリトライするか
*/
public function run(callable $operation, ?callable $retryOn = null): mixed
{
$retryOn ??= fn(\Throwable $e) => true;
$attempt = 0;
while (true) {
$attempt++;
try {
$result = $operation();
if ($attempt > 1) {
echo "成功({$attempt}回目)\n";
}
return $result;
} catch (\Throwable $e) {
if ($attempt >= $this->maxAttempts || !$retryOn($e)) {
throw $e;
}
$delay = $this->calcDelay($attempt);
echo "失敗({$attempt}回目): {$e->getMessage()}\n";
echo " {$delay}秒後にリトライ...\n";
// usleep でミリ秒精度の待機
usleep((int) ($delay * 1_000_000));
}
}
}
/**
* 指数バックオフ + ジッター(揺らぎ)で待機時間を計算する
* ジッターを加えることで複数クライアントの同時リトライを分散する
*/
private function calcDelay(int $attempt): float
{
$base = $this->baseDelaySeconds * ($this->multiplier ** ($attempt - 1));
$capped = min($base, $this->maxDelaySeconds);
// ±jitterFactor の揺らぎを追加
$jitter = $capped * $this->jitterFactor * (mt_rand(-100, 100) / 100);
return max(0.1, $capped + $jitter);
}
}
$retry = new ExponentialBackoffRetry(
maxAttempts: 4,
baseDelaySeconds: 1.0,
multiplier: 2.0
);
$callCount = 0;
try {
$result = $retry->run(
operation: function () use (&$callCount): string {
$callCount++;
if ($callCount < 3) {
throw new \RuntimeException("APIレート制限(429)");
}
return "成功レスポンス";
},
retryOn: fn(\Throwable $e) => str_contains($e->getMessage(), '429')
);
echo "結果: {$result}\n";
} catch (\Throwable $e) {
echo "最終失敗: {$e->getMessage()}\n";
}
/*
出力例:
失敗(1回目): APIレート制限(429)
1.0秒後にリトライ...
失敗(2回目): APIレート制限(429)
2.0秒後にリトライ...
成功(3回目)
結果: 成功レスポンス
*/
例2:ポーリング処理クラス
<?php
/**
* 非同期ジョブの完了を定期的にポーリングするクラス
* sleep() で一定間隔を空けながらステータスを確認する
*/
class JobPoller
{
public function __construct(
private readonly int $intervalSeconds = 2,
private readonly int $timeoutSeconds = 60,
private readonly bool $verbose = true
) {}
/**
* 条件が満たされるまでポーリングを続ける
*
* @param callable(): mixed $checker ステータス確認処理(完了時に非null を返す)
* @param callable(mixed): bool $isDone 完了判定
*/
public function waitUntil(callable $checker, callable $isDone): mixed
{
$startTime = time();
$attempt = 0;
while (true) {
$attempt++;
$elapsed = time() - $startTime;
if ($elapsed >= $this->timeoutSeconds) {
throw new \RuntimeException(
"タイムアウト: {$this->timeoutSeconds}秒以内に完了しませんでした"
);
}
$status = $checker();
if ($this->verbose) {
$statusStr = is_array($status)
? json_encode($status, JSON_UNESCAPED_UNICODE)
: (string) $status;
echo "[{$elapsed}秒経過 / 試行{$attempt}回] ステータス: {$statusStr}\n";
}
if ($isDone($status)) {
echo "完了({$elapsed}秒 / {$attempt}回のポーリング)\n";
return $status;
}
// タイムアウトまでの残り時間と通常インターバルの小さい方だけ待つ
$remaining = $this->timeoutSeconds - $elapsed;
$waitSeconds = min($this->intervalSeconds, $remaining);
if ($waitSeconds > 0) {
sleep((int) $waitSeconds);
}
}
}
}
// 擬似的な非同期ジョブのステータスチェック
$jobStartedAt = time();
$poller = new JobPoller(intervalSeconds: 1, timeoutSeconds: 10);
try {
$result = $poller->waitUntil(
checker: function () use ($jobStartedAt): array {
$elapsed = time() - $jobStartedAt;
// 3秒後に完了するシミュレーション
return [
'status' => $elapsed >= 3 ? 'completed' : 'running',
'progress' => min(100, $elapsed * 33),
];
},
isDone: fn(array $s) => $s['status'] === 'completed'
);
echo "ジョブ完了: " . json_encode($result, JSON_UNESCAPED_UNICODE) . "\n";
} catch (\RuntimeException $e) {
echo "エラー: {$e->getMessage()}\n";
}
例3:レート制限付きバッチ処理クラス
<?php
/**
* 大量データのバッチ処理でAPIや DB への負荷を制御するクラス
* sleep() / usleep() で処理間隔を設けてレート制限を守る
*/
class RateLimitedBatchProcessor
{
private int $processedCount = 0;
private float $totalSleepTime = 0;
private float $startTime;
public function __construct(
private readonly int $batchSize = 100,
private readonly float $delayBetweenBatches = 1.0, // バッチ間の待機(秒)
private readonly float $delayBetweenItems = 0.01, // アイテム間の待機(秒)
private readonly int $maxPerSecond = 0 // 0=無制限
) {
$this->startTime = microtime(true);
}
/**
* アイテムの配列をバッチ処理する
*
* @param iterable $items 処理対象のアイテム
* @param callable $processor 各アイテムの処理関数
*/
public function process(iterable $items, callable $processor): array
{
$results = [];
$batch = [];
$batchCount = 0;
foreach ($items as $item) {
$batch[] = $item;
if (count($batch) >= $this->batchSize) {
$batchCount++;
$results = array_merge($results, $this->processBatch($batch, $processor, $batchCount));
$batch = [];
// バッチ間の待機
if ($this->delayBetweenBatches > 0) {
echo "バッチ{$batchCount}完了: {$this->delayBetweenBatches}秒待機\n";
usleep((int) ($this->delayBetweenBatches * 1_000_000));
$this->totalSleepTime += $this->delayBetweenBatches;
}
}
}
// 残りのアイテムを処理
if (!empty($batch)) {
$batchCount++;
$results = array_merge($results, $this->processBatch($batch, $processor, $batchCount));
}
return $results;
}
private function processBatch(array $batch, callable $processor, int $batchNo): array
{
$results = [];
$batchStartTime = microtime(true);
foreach ($batch as $item) {
$results[] = $processor($item);
$this->processedCount++;
// アイテム間の待機
if ($this->delayBetweenItems > 0) {
usleep((int) ($this->delayBetweenItems * 1_000_000));
$this->totalSleepTime += $this->delayBetweenItems;
}
// レート制限チェック(maxPerSecond が設定されている場合)
if ($this->maxPerSecond > 0) {
$this->enforceRateLimit($batchStartTime, count($results));
}
}
return $results;
}
/**
* 処理速度が maxPerSecond を超えないよう調整する
*/
private function enforceRateLimit(float $startTime, int $count): void
{
$elapsed = microtime(true) - $startTime;
$expected = $count / $this->maxPerSecond;
if ($expected > $elapsed) {
$waitMicro = (int) (($expected - $elapsed) * 1_000_000);
usleep($waitMicro);
$this->totalSleepTime += ($expected - $elapsed);
}
}
public function getStats(): array
{
$elapsed = microtime(true) - $this->startTime;
return [
'processed' => $this->processedCount,
'elapsed_sec' => round($elapsed, 2),
'total_sleep_sec' => round($this->totalSleepTime, 2),
'active_sec' => round($elapsed - $this->totalSleepTime, 2),
'avg_per_sec' => $elapsed > 0
? round($this->processedCount / $elapsed, 1)
: 0,
];
}
}
$processor = new RateLimitedBatchProcessor(
batchSize: 3,
delayBetweenBatches: 0.1,
delayBetweenItems: 0.0
);
$items = range(1, 7);
$results = $processor->process($items, fn($n) => $n * 2);
echo "処理結果: " . implode(', ', $results) . "\n";
print_r($processor->getStats());
/*
出力例:
バッチ1完了: 0.1秒待機
バッチ2完了: 0.1秒待機
処理結果: 2, 4, 6, 8, 10, 12, 14
Array (
[processed] => 7
[elapsed_sec] => 0.22
[total_sleep_sec] => 0.2
[active_sec] => 0.02
[avg_per_sec] => 31.8
)
*/
例4:スケジューラー・定期実行クラス
<?php
/**
* sleep() を使ってCLIスクリプト内でタスクを定期実行するクラス
* cron が使えない環境や細かいインターバル制御に有効
*/
class SimpleScheduler
{
private bool $running = false;
private array $tasks = [];
private array $lastRuns = [];
/**
* タスクを登録する
*
* @param string $name タスク名
* @param int $interval 実行間隔(秒)
* @param callable $callback 実行する処理
*/
public function addTask(string $name, int $interval, callable $callback): void
{
$this->tasks[$name] = ['interval' => $interval, 'callback' => $callback];
$this->lastRuns[$name] = 0;
echo "タスク登録: {$name}({$interval}秒間隔)\n";
}
/**
* スケジューラーを起動して定期実行ループを開始する
*
* @param int $runFor 実行する秒数(0 = 無限ループ)
* @param int $tick ループの最小間隔(秒)
*/
public function run(int $runFor = 0, int $tick = 1): void
{
$this->running = true;
$startTime = time();
echo "スケジューラー起動\n";
while ($this->running) {
$now = time();
// 実行時間制限チェック
if ($runFor > 0 && ($now - $startTime) >= $runFor) {
echo "実行時間 {$runFor}秒 経過・終了\n";
break;
}
// 各タスクの実行チェック
foreach ($this->tasks as $name => $task) {
if (($now - $this->lastRuns[$name]) >= $task['interval']) {
$this->runTask($name, $task['callback'], $now);
}
}
sleep($tick);
}
}
public function stop(): void
{
$this->running = false;
}
private function runTask(string $name, callable $callback, int $now): void
{
$this->lastRuns[$name] = $now;
$start = microtime(true);
try {
$callback();
$elapsed = round((microtime(true) - $start) * 1000, 1);
echo "[" . date('H:i:s') . "] {$name} 実行完了({$elapsed}ms)\n";
} catch (\Throwable $e) {
echo "[" . date('H:i:s') . "] {$name} エラー: {$e->getMessage()}\n";
}
}
}
$scheduler = new SimpleScheduler();
$scheduler->addTask('キャッシュクリア', 5, function (): void {
echo " → キャッシュをクリアしました\n";
});
$scheduler->addTask('ハートビート', 2, function (): void {
echo " → ハートビート送信\n";
});
// 10秒間実行するデモ
// $scheduler->run(runFor: 10, tick: 1);
例5:ウォームアップ・スロットルクラス
<?php
/**
* 処理速度を徐々に上げる「ウォームアップ」と
* 速度上限を守る「スロットリング」を組み合わせたクラス
* DB・API・キャッシュへの急激な負荷を防ぐ
*/
class ThrottledProcessor
{
private int $processedCount = 0;
private float $windowStartTime;
private int $windowCount = 0;
public function __construct(
private readonly int $maxPerWindow = 10, // ウィンドウあたりの最大処理数
private readonly float $windowSeconds = 1.0, // ウィンドウサイズ(秒)
private readonly int $warmupCount = 0, // ウォームアップ件数
private readonly float $warmupDelaySeconds = 0.5 // ウォームアップ中の待機時間
) {
$this->windowStartTime = microtime(true);
}
/**
* スロットル制御付きで1件処理する
*/
public function process(callable $operation): mixed
{
$this->throttle();
$result = $operation();
$this->processedCount++;
$this->windowCount++;
return $result;
}
private function throttle(): void
{
// ウォームアップ期間中は追加ディレイ
if ($this->warmupCount > 0 && $this->processedCount < $this->warmupCount) {
$warmupProgress = $this->processedCount / $this->warmupCount;
$delay = $this->warmupDelaySeconds * (1 - $warmupProgress);
if ($delay > 0.001) {
usleep((int) ($delay * 1_000_000));
}
return;
}
// スライディングウィンドウでレート制限
$now = microtime(true);
$elapsed = $now - $this->windowStartTime;
if ($elapsed >= $this->windowSeconds) {
// ウィンドウをリセット
$this->windowStartTime = $now;
$this->windowCount = 0;
return;
}
if ($this->windowCount >= $this->maxPerWindow) {
// ウィンドウが終わるまで待機
$waitSec = $this->windowSeconds - $elapsed;
echo " スロットル: {$waitSec}秒待機\n";
usleep((int) ($waitSec * 1_000_000));
$this->windowStartTime = microtime(true);
$this->windowCount = 0;
}
}
public function getProcessedCount(): int
{
return $this->processedCount;
}
}
$throttle = new ThrottledProcessor(
maxPerWindow: 5,
windowSeconds: 1.0,
warmupCount: 3,
warmupDelaySeconds: 0.2
);
for ($i = 1; $i <= 8; $i++) {
$throttle->process(function () use ($i): void {
echo "処理 #{$i}\n";
});
}
echo "合計: " . $throttle->getProcessedCount() . " 件処理\n";
例6:精密なタイミング制御クラス
<?php
/**
* sleep() / usleep() / time_sleep_until() を使い分けて
* 精密な時間制御を行うクラス
* メトロノーム的な一定間隔実行・フレームレート制御などに使用する
*/
class PreciseTimer
{
private float $lastTickTime;
private int $tickCount = 0;
private array $tickLog = [];
public function __construct(
private readonly float $targetIntervalSeconds
) {
$this->lastTickTime = microtime(true);
}
/**
* 前回のtickからの経過時間が targetInterval になるまで待機する
* 処理時間を考慮して正確な間隔を保つ
*/
public function tick(): float
{
$this->tickCount++;
$now = microtime(true);
$elapsed = $now - $this->lastTickTime;
$waitTime = $this->targetIntervalSeconds - $elapsed;
if ($waitTime > 0) {
// time_sleep_until で目標時刻まで精密に待機
$targetTime = $this->lastTickTime + $this->targetIntervalSeconds;
time_sleep_until($targetTime);
}
$actualInterval = microtime(true) - $this->lastTickTime;
$this->lastTickTime = microtime(true);
$this->tickLog[] = [
'tick' => $this->tickCount,
'interval' => round($actualInterval * 1000, 2),
'drift_ms' => round(($actualInterval - $this->targetIntervalSeconds) * 1000, 2),
];
return $actualInterval;
}
/**
* 指定回数のtickを実行してタイミング精度を計測する
*/
public function benchmark(int $ticks, callable $work): array
{
$this->tickLog = [];
$this->tickCount = 0;
$this->lastTickTime = microtime(true);
for ($i = 0; $i < $ticks; $i++) {
$work($i + 1);
$this->tick();
}
$intervals = array_column($this->tickLog, 'interval');
$drifts = array_column($this->tickLog, 'drift_ms');
return [
'ticks' => $ticks,
'target_ms' => $this->targetIntervalSeconds * 1000,
'avg_interval_ms'=> round(array_sum($intervals) / count($intervals), 2),
'max_drift_ms' => round(max(array_map('abs', $drifts)), 2),
'avg_drift_ms' => round(array_sum(array_map('abs', $drifts)) / count($drifts), 2),
];
}
public function getTickLog(): array
{
return $this->tickLog;
}
}
// 0.1秒間隔で5回実行してタイミング精度を計測
$timer = new PreciseTimer(targetIntervalSeconds: 0.1);
$stats = $timer->benchmark(
ticks: 5,
work: fn(int $n) => null // 実際には何らかの処理
);
echo "タイミング精度レポート:\n";
foreach ($stats as $key => $val) {
printf(" %-20s: %s\n", $key, $val);
}
/*
出力例:
タイミング精度レポート:
ticks : 5
target_ms : 100
avg_interval_ms : 100.12
max_drift_ms : 0.38
avg_drift_ms : 0.21
*/
sleep() を使う際の注意事項
Webリクエスト内での使用
<?php
// ⚠️ Webリクエスト内で sleep() を使うと、その間ユーザーは待ち続ける
// max_execution_time も消費し続ける
// ❌ NG:Webリクエストで長時間 sleep
sleep(30); // ユーザーが30秒待つ・タイムアウトリスク
// ✅ 長時間処理はキューに積んでCLIで非同期実行する
// $queue->push(['job' => 'send_email', 'user_id' => 42]);
// return response('メール送信を受け付けました');
set_time_limit() との関係
<?php
// sleep() で停止している時間は max_execution_time に加算されない
// (UNIX環境のみ。Windowsでは加算される)
set_time_limit(10);
sleep(5); // この5秒は実行時間にカウントされない(UNIX)
// 実際の処理に10秒かけられる
シグナルによる中断
<?php
// sleep() はシグナルによって中断される場合がある
// 戻り値が 0 でなければ中断された(残り秒数が返る)
$remaining = sleep(10);
if ($remaining > 0) {
echo "シグナルで中断: 残り{$remaining}秒\n";
}
関連関数との比較
| 関数 | 精度 | 戻り値 | 特徴 |
|---|
sleep(int $sec) | 秒 | 残り秒数 or 0 | 最もシンプル |
usleep(int $micro) | マイクロ秒 | void | ミリ秒制御に最適 |
time_nanosleep(int, int) | ナノ秒 | true or array | 最高精度、残り時間も取得可 |
time_sleep_until(float) | マイクロ秒 | bool | 指定時刻まで待機 |
よくある落とし穴
<?php
// ❌ NG:PHP 8.0以降、負の値は ValueError
sleep(-1); // ValueError: sleep(): Argument #1 ($seconds) must be greater than or equal to 0
// ✅ 0以上であることを確認する
$seconds = max(0, $calculatedDelay);
sleep($seconds);
<?php
// ❌ よくある誤解:sleep() が max_execution_time を消費すると思っている
// UNIX 環境では sleep 中の時間は実行時間にカウントされない
ini_set('max_execution_time', 5);
sleep(10); // UNIX では問題なし(Windows では Fatal error になる)
echo "完了\n";
// ✅ ポータブルなコードでは注意が必要
<?php
// ❌ 精度が必要なのに sleep() を使っている
sleep(1); // 最小単位が1秒なので細かい制御ができない
// ✅ ミリ秒制御には usleep() を使う
usleep(500_000); // 500ms
usleep(100_000); // 100ms
usleep(50_000); // 50ms
まとめ
| 項目 | 内容 |
|---|
| 関数名 | sleep(int $seconds): int |
| 主な用途 | スクリプト実行を秒単位で一時停止する |
| 戻り値 | 正常終了: 0 / シグナル中断: 残り秒数 |
| 精度 | 秒単位(細かい制御には usleep() を使う) |
| ミリ秒待機 | usleep(N * 1_000) |
| 指定時刻まで待機 | time_sleep_until(microtime(true) + $seconds) |
| max_execution_time | UNIX環境では sleep 中は加算されない |
| Webリクエストでの使用 | 原則避ける(長時間処理はキュー化を推奨) |
sleep() はシンプルながら、リトライの間隔制御・ポーリング・バッチ処理の負荷分散・定期実行など実践的な場面で欠かせない関数です。秒精度で十分な場面では sleep()、ミリ秒・マイクロ秒が必要なら usleep()、特定時刻まで待ちたいなら time_sleep_until() と使い分けることで、精度と可読性を両立できます。
参考リンク