コードを眺めていると、std::this_thread::yield()
が突然視線を集めました。C11 の構文糖で、これほど多く使われていたのは初めてです。yield
を以前は目にすることはありませんでした。
マニュアルを確認せず、まず思い浮かべたのは、それが非同期処理と関連しているのではないかということでした。yield
は boost 協程の実装の中に見られる単語であり、ここでは非同期処理とは関係ありません。制御ロジックは通常のスレッドに関連しています。
ドキュメント
yield
この関数の正確性は、実装に依存し、特に使用されている OS のスケジューラメカニズムとシステムの状態に依存します。例えば、先入れ先出しリアルタイムスケジューラ(Linux の SCHED_FIFO)が現在のスレッドをサスペンドし、それを実行可能な同優先度のスレッドのキューの末尾に置く(同優先度で他のスレッドがない場合、yield は効果がない)といった具合です。
sleep_for
現在のスレッドの実行をブロックし、指定された sleep_duration
分間少なくとも停止します。
この関数は、スケジューリングやリソース競合による遅延のため、sleep_duration
より長くブロックされる可能性があります。
標準ライブラリでは、安定したクロックを使用して時間を測定することをお勧めします。システム時間で実装する場合は、待機時間がクロック調整に敏感になる可能性があることに注意してください。
分析
両方の関数は、現在のスレッドがスレッドを占有しないようにし、実行効果はプラットフォームによって異なる可能性があります。ここまでの内容でまだ少し理解が曖昧ですが、コードを実行して結果を確認してみましょう。
ThinkPad ノートパソコン(Visual Studio Community 2022)、腾讯云 S2 標準サーバー(gcc8.5)
分析
実行プラットフォーム | 関数 | 初回/us | 二次/us | 三次/us |
---|---|---|---|---|
Windows | sleep_for | 9872 | 1884 | 11302 |
分析
実行プラットフォーム | 関数 | 初回/US | 二回目/US | 三回目/US |
---|
分析
実行プラットフォーム | 関数 | 初回/us | 二回/us | 三回/us |
---|---|---|---|---|
Linux | sleep_for | 171 | 168 | 167 |
分析
実行プラットフォーム | 関数 | 初回/us | 二次/us | 三次/us |
---|---|---|---|---|
Linux | yield | 101 | 102 | 101 |
分析
実行結果から判断すると、オペレーティングシステムの異なる実装により、高精度なスリープ(休眠)時の sleep_for
の安定性差が非常に大きいことがわかります。高精度なスリープを実現するためには、yield
を使用する方が適しています。
時間精度を ms
(ミリ秒) に向上させた場合、両者の差異はほとんど見られなくなります。
#include <iostream>
#include <chrono>
#include <thread>
// 別のスレッドで短い時間の“忙しいスリープ”を実行することを推奨します
void little_sleep(std::chrono::microseconds us)
{
auto start = std::chrono::high_resolution_clock::now();
auto end = start + us;
do {
std::this_thread::yield();
} while (std::chrono::high_resolution_clock::now() < end);
}
int main()
{
auto start = std::chrono::high_resolution_clock::now();
little_sleep(std::chrono::microseconds(100));
std::this_thread::sleep_for(std::chrono::microseconds(100));
auto elapsed = std::chrono::high_resolution_clock::now() - start;
std::cout << "waited for "
<< std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count()
<< " microseconds\n";
}