円周率を計算するようなホビープログラムに限らず、グラフ探索・組み合わせ探索のように長時間CPUを使うワークというのは実務でも出てきます。
こういうとき、UI設計の基本として「重い処理はワーカースレッドに任せ、UIスレッドはすぐに処理を返す」というのは最近では常識になってきた感がありますし、C#のasync/awaitのように処理系側のサポートも充実してきました。
これに加えてもう一つ大事な基本があります。
【CPUをちょくちょく解放すること】
です。
シングルコアのマシンで特に顕著な影響が出ますが、長時間CPUを使う計算をしていると他のプロセスはまったく動けなくなります。OSシェルさえ。マルチコアマシンでも、コア数と同じ数だけそういう重いプロセスが出現した時点で同じことになります。
プリエンプティブマルチタスクで動作する現代のOSでも、自発的にCPUを解放しないプロセスは高頻度に制御を奪われることなくある程度の長時間単独で走ってしまうものなのです。
ここら辺の事情をわかった上で、ループの先頭や最後に sleep(1); などとCPU解放処理を入れている方もおられることと思います。
しかし sleep(1); 、不合理ですよね。もしCPUを待っているプロセスがいなかった場合も、必ず1ミリ秒のタイムロスが入ってしまいます(OS実装次第で1ミリ秒よりも長くなります)。
つまり、待っているプロセスがいるならCPU解放、いなければそのまま続行という、sleep(0); 的な記述が本当はしたいわけです(実際にはこのコールは無視されます)。
yield
待っているプロセスがいるならCPU解放、いなければそのまま続行というシステムコールは存在し、一般的にはyieldと呼ばれます。
- POSIXなら sched_yield()
- Win32なら SleepEx(0,0)
- Javaなら Thread.yield()
- .NETなら Thread.Yield()
- C++なら std::this_thread::yield()
です。Javaだと Thread.sleep(1) に比べて try-catch で囲む必要もないのでコードもすっきりしてなお良いですね。