Pythonで処理をミリ秒単位で一時停止させる場面は、スクレイピングのアクセス間隔制御や高頻度なデータ処理など、正確なタイミング制御が求められる文脈で頻繁に発生します。待機処理の実装でよく使われるのが「timeモジュールのsleep」で、小数を指定することによって1秒未満の細かい待機時間を簡単に実現できます。
この記事では、Pythonにおけるミリ秒単位の待機処理の基本的な使い方を解説していきます。指定した時間通りに止まらないOSのタイマー解像度の問題や高精度な時間制御を実現する代替手法など、サンプルコード付きで解説していきますので、ぜひ参考にしてください。
目次
Pythonのsleep関数でミリ秒を指定する方法
標準ライブラリのtimeモジュールを活用すると、プログラムの実行を任意の間隔で一時停止できます。引数に小数を渡すことによって、1秒未満の細かい数値を指定可能です。
ここでは、以下の2つの実装パターンについて解説します。
- 小数を用いてミリ秒を指定する実装例
- APIリクエスト間隔を制御する実装例
それぞれの具体的な記述方法と活用場面を見ていきましょう。
小数を用いてミリ秒を指定する実装例
Pythonの処理を一時停止させる場合、組み込みモジュールをインポートして待機時間を秒単位で渡します。引数に0.001を指定すると、理論上は1ミリ秒の待機処理として機能します。
小数を活用して待機時間を設定する基本的な記述方法は、以下のとおりです。
import time
# 1ミリ秒(0.001秒)待機する
time.sleep(0.001)
# 500ミリ秒(0.5秒)待機する
time.sleep(0.5)
上記のコードでは、指定した秒数だけカレントスレッドの実行を一時的にブロックし、CPUの利用権限をOSに返却しています。指定時間は最小の待機時間として扱われ、OSのスケジューリングの影響で常に正確なミリ秒で再開されるわけではない点を把握しておくと役立ちます。
APIリクエスト間隔を制御する実装例
外部サーバーへ連続してアクセスする際、短時間に大量の通信を発生させないための負荷軽減策として待機処理を利用します。適切な間隔を空けることによって、アクセス制限や遮断の対象となるリスクを回避できます。
ループ処理の中で一時停止を挟み、リクエスト間隔を一定に保つコードは以下のようです。
import time
api_endpoints = ["endpoint1", "endpoint2", "endpoint3"]
for endpoint in api_endpoints:
# APIへリクエストを送信するダミー処理
print(f"{endpoint}へリクエストを送信しました")
# サーバー負荷を軽減するために100ミリ秒待機する
time.sleep(0.1)
上記のコードでは、リスト内の要素を処理するたびに0.1秒の待機時間を設け、意図しないサーバーへの負荷集中を防いでいます。定期的なデータ収集やアニメーションのフレームレート調整など、一定の周期を持たせたい場面でも同様に適用できます。
Pythonのミリ秒待機が不正確になる原因
Pythonで短い時間を待機させる際、指定した時間通りに止まらない現象が頻繁に発生します。これは、ハードウェアやシステムの仕様が大きく関わっています。
- OSのタイマー分解能による要因
- ブロッキング処理の仕様による要因
これらの要因を把握しておくと役立ちます。特にWindows環境やシステム負荷が高い状況では、遅延が顕著に現れる傾向があります。
ここでは、待機時間が不正確になる根本的な理由を順番に解説していきます。
OSのタイマー分解能による要因
以下のコードは、0.001秒の待機を指定して実際の経過時間を高精度に計測する例です。
import time
start_time = time.perf_counter()
time.sleep(0.001)
end_time = time.perf_counter()
print(end_time - start_time)
上記のコードでは、Python 3.11未満かつWindowsのタイマー分解能が改善される前の環境(Windows 8.1未満)で実行すると、約15.6ミリ秒前後の経過時間が表示されるケースがあります。これは、Windowsのデフォルトのタイマー分解能が約15.6ミリ秒であることに起因しています。
ただし、Python 3.11以降(Windows 8.1以降、公式What's New 3.11より)では内部実装が改善されており、この遅延は大幅に軽減されています。
指定した時間はあくまで最小の待機時間として扱われます。Pythonの公式ドキュメントでも、オペレーティングシステムのスケジューラに依存するため精度が落ちる場合があると説明されています。
PythonのバージョンやWindowsのバージョン、システムの負荷状況によって実際の待機時間は変動するため、環境ごとの差異を考慮した実装が求められます。
ブロッキング処理の仕様による要因
以下のコードは、ループ内で待機処理を実行して経過時間を測定する例です。
import time
for _ in range(3):
start = time.perf_counter()
time.sleep(0.01)
end = time.perf_counter()
print(end - start)
上記のコードでは、実行のたびに待機時間にばらつきが生じます。精度低下の主要因は、OSのスケジューラによる再開タイミングの不確定性です。
システムが別のタスクにCPUを割り当てた場合、再度プログラムに処理が戻るまでに時間がかかるため、指定値よりも長い待機時間になるケースが発生します。
なお、CPythonのデフォルトビルドでは、time.sleep()はGIL(Global Interpreter Lock、Pythonインタープリタが同時に実行できるスレッドを1つに制限するロック機構)を解放するため、待機中も他のスレッドは動作できます。スレッド全体が停止するわけではない点を把握しておくと役立ちます。
厳密なタイミング制御が求められる場面では、次のセクションで解説する代替手法の検討が必要です。
Pythonで高精度なミリ秒待機を実現する手法
標準の待機処理では、OSの仕様や実行環境の影響を受けて精度が低下するケースがあります。より厳密な時間制御が求められる状況では、代替手段の導入が効果的な場合があります。
精度を改善できる可能性がある手法として、以下のアプローチが存在します。要件に応じて適切なものを選択してください。
- OSのタイマー分解能を一時的に向上させる(Python 3.10以前のWindows向け)
- CPUを占有して待機時間を厳密に制御する
- 最新バージョンのPythonが提供する機能を利用する
各手法にはそれぞれメリットとデメリットがあります。システム全体の負荷や実行環境を考慮して導入を検討しましょう。
ここでは、それぞれの具体的な実装方法や特徴について、サンプルコードを交えて解説していきます。
OSのタイマー精度を変更する手法
Windows環境において、標準のタイマー分解能は約15.6ミリ秒に設定されています。この分解能をctypesライブラリを用いて一時的に変更し、精度を向上させます。
以下のコードは、タイマー精度を1ミリ秒に変更してから待機処理を実行する例です。
import time
import ctypes
# タイマー精度を1ミリ秒に変更
ctypes.windll.winmm.timeBeginPeriod(1)
try:
start = time.perf_counter()
time.sleep(0.001)
end = time.perf_counter()
print(f"待機時間: {end - start:.4f}秒")
finally:
# タイマー精度を元に戻す
ctypes.windll.winmm.timeEndPeriod(1)
上記のコードでは、timeBeginPeriodを呼び出して精度を変更しています。処理が完了した後は、システムへの影響を防ぐために必ずtimeEndPeriodで設定を戻してください。
このアプローチは主にPython 3.10以前のWindows環境での対策として有効です。timeBeginPeriodのタイマー分解能への影響は、Windowsのバージョンによって異なります。
Windows 10 version 2004より前ではシステム全体のタイマー分解能を変更するため、他のプロセスにも影響を与えます。一方、Windows 10 version 2004以降では主に呼び出したプロセスに対してのみ適用されるよう仕様が変更されています。
また、Microsoftの公式ドキュメントでは過度な使用がシステム全体の消費電力を増加させる可能性があると説明されているため、必要な期間のみ使用し確実に設定を元に戻す実装が求められます。Python 3.11以降(Windows 8.1以降)ではtime.sleep()自体が改善されているため、このアプローチが必要なシーンは限定的です(詳細は「Python 3.11以降の機能を利用する手法」を参照)。
無限ループを利用する手法
通常の待機処理はCPUの利用権限をOSに返却するため、再開時に遅延が発生しやすくなります。この問題を回避するために、無限ループ内で現在時刻を監視し続ける「Busy-wait(Spin loop)」という手法が用いられます。
無限ループを利用して1ミリ秒の待機を実現する使い方は以下の通りです。
import time
def spin_sleep(target_seconds):
target_time = time.perf_counter() + target_seconds
while time.perf_counter() < target_time:
pass
start = time.perf_counter()
spin_sleep(0.001)
end = time.perf_counter()
print(f"待機時間: {end - start:.4f}秒")
上記のコードでは、timeモジュールのperf_counterを使って経過時間を高精度に計測しています。CPU消費と引き換えに短い待機のばらつきを抑えやすい手法ですが、OSのプリエンプションにより遅延が生じる可能性はゼロではありません。
待機中は1CPUコアを大きく消費し、使用率が高止まりしやすいというデメリットがあります。これは通常時にOSへCPUを返却するtime.sleep()とは対照的な挙動です。
他のプロセスの動作を妨げる可能性があるため、短時間・限定用途でのみ使用し、通常の実装では非推奨の手法として扱ってください。
Python 3.11以降の機能を利用する手法
Python 3.11では、標準の待機処理の内部実装が改善されています。Unix環境においては、マイクロ秒の解像度だったものがナノ秒の解像度へと向上しています(clock_nanosleepやnanosleepを利用)。
さらにWindows環境でも、Python 3.11以降(Windows 8.1以降、What's New 3.11より)ではtime.sleep()がhigh-resolution waitable timerを使用するように改善されています。なお、現行の公式timeドキュメントではWindows 10以降と記載されており、公式ドキュメント間で表記に差異がある点に留意してください。
従来の約1ミリ秒分解能から100ナノ秒分解能へと大幅に向上しており、最新環境では追加の手法を使わずとも精度が改善される場合があります。
新しいバージョンの機能を活かして待機処理を実行するコードは以下の通りです。
import time
# Python 3.11以降(Windows 8.1以降 / Unix)で内部実装が改善
start = time.perf_counter()
time.sleep(0.001)
end = time.perf_counter()
print(f"待機時間: {end - start:.4f}秒")
上記のコードでは、標準の関数を呼び出すだけでPython 3.11以降の改善された実装が自動的に適用されます。ただし、この内部実装の変更はOSスケジューリングによる遅延を完全には排除しておらず、指定時間ぴったりの待機を保証するものではない点を把握しておくと役立ちます。
Pythonのsleep関数によるミリ秒待機に関するよくある質問
time.sleepとasyncio.sleepのどちらを使えばよいですか?
同期処理(通常のプログラム)ではtime.sleepを使用し、asyncioを用いた非同期処理ではasyncio.sleepを使用します。非同期環境でtime.sleepを使うと、待機中に他の非同期タスクへ処理を譲れないため、プログラム全体が意図せずブロックされます。
async関数の中で待機処理を書く場合は、await asyncio.sleep(秒数)の形式で記述してください。通常の関数やスクリプトのトップレベルではtime.sleep(秒数)を使用するのが基本です。
time.sleep関数はCPUの使用率を大幅に上げますか?
この処理はOSにCPUの利用権限を返却するため、待機中のCPU使用率が大幅に上がることはありません。呼び出したスレッドのみを停止させるブロッキング型の動作が特徴で、CPythonのデフォルトビルドではGILを解放するため他のスレッドは待機中も動作できます。
ただし、より高精度なミリ秒単位の制御を求めて無限ループを利用する待機手法を採用した場合は、CPUコアを継続的に消費します。処理の精度とリソース負荷のトレードオフを考慮して実装手法を選択してください。
※上記コンテンツの内容やソースコードはAIで確認・デバッグしておりますが、間違いやエラー、脆弱性などがある場合は、コメントよりご報告いただけますと幸いです。
ITやプログラミングに関するコラム
PythonをWebで実行する方法
共通テスト「情報Ⅰ」2年目で変わる、日本の教育と学び方
gitでブランチ(branch)を切り替える方法
git cloneでブランチを指定する方法
64GBのメモリが必要な人・不要な人の特徴
PCを再起動するコマンド一覧
CapsLock以外で大文字になる原因【Windows編】
パソコンで大文字になるのを解除する方法
面白いAIの活用事例を業界別に紹介
Gitでcommit(コミット)を取り消す方法
ITやプログラミングに関するニュース
サイボウズがkintone AIを正式提供、β版から約1年を経てクレジット制を導入
ロゼッタのラクヤクAIがCSRドラフト作成期間を90%以上短縮、従来4週間を約2日に
AI CROSSが不動産業界向け生成AI伴走支援を開始、アスコットの業務AI実装を実践サポート
日本情報クリエイトが「オーナー提案AIロボⅡ」売買査定を刷新、月1万円からW査定が回数無制限に
Wur株式会社がAI新規事業診断サービス「MVP事業診断レポート」をリリース、12の質問で事業構想を約10分で分析
バトンズがM&A専門家向け「AI概要書」β版を提供開始、企業概要書のドラフトを最速3分で自動生成
SCSKが観光DXサービス「Connexia」を開発、首里城公園でNFT活用の周遊促進が始動
Verdent AI発表、エンジニア不要でソフトウェアを構築する「AIエンジニアリングチーム」が登場
ゼネラルBREXAテクノロジーが外食・小売向けAIサービス「aimana」を開発、店長の意思決定をデータで支援
田中組がKencopa工程AIエージェント製品版を先行利用開始、建設現場の工程管理属人化を解消へ
