C++11 线程等待行为:std::this_thread::yield() 与 std::this_thread::sleep_for( std::chrono::milliseconds(1) )
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17325888/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C++11 Thread waiting behaviour: std::this_thread::yield() vs. std::this_thread::sleep_for( std::chrono::milliseconds(1) )
提问by Thomas Russell
I was told when writing Microsoft specific C++ code that writing Sleep(1)
is much better than Sleep(0)
for spinlocking, due to the fact that Sleep(0)
will use more of the CPU time, moreover, it only yields if there is another equal-prioritythread waiting to run.
我在编写 Microsoft 特定的 C++ 代码时被告知,编写Sleep(1)
比Sleep(0)
自旋锁好得多,因为这Sleep(0)
将使用更多的 CPU 时间,而且只有在有另一个同等优先级的线程等待运行时才会产生。
However, with the C++11 thread library, there isn't much documentation (at least that I've been able to find) about the effects of std::this_thread::yield()
vs. std::this_thread::sleep_for( std::chrono::milliseconds(1) )
; the second is certainly more verbose, but are they both equally efficient for a spinlock, or does it suffer from potentially the same gotchas that affected Sleep(0)
vs. Sleep(1)
?
但是,对于 C++11 线程库,关于std::this_thread::yield()
vs.效果的文档并不多(至少我已经找到了)std::this_thread::sleep_for( std::chrono::milliseconds(1) )
。第二个当然更冗长,但它们对于自旋锁是否同样有效,或者它是否遭受影响Sleep(0)
vs. 的潜在相同问题Sleep(1)
?
An example loop where either std::this_thread::yield()
or std::this_thread::sleep_for( std::chrono::milliseconds(1) )
would be acceptable:
一个示例循环,其中std::this_thread::yield()
或std::this_thread::sleep_for( std::chrono::milliseconds(1) )
将是可接受的:
void SpinLock( const bool& bSomeCondition )
{
// Wait for some condition to be satisfied
while( !bSomeCondition )
{
/*Either std::this_thread::yield() or
std::this_thread::sleep_for( std::chrono::milliseconds(1) )
is acceptable here.*/
}
// Do something!
}
回答by ComicSansMS
The Standard is somewhat fuzzy here, as a concrete implementation will largely be influenced by the scheduling capabilities of the underlying operating system.
标准在这里有些模糊,因为具体的实现在很大程度上会受到底层操作系统的调度能力的影响。
That being said, you can safely assume a few things on any modern OS:
话虽如此,您可以在任何现代操作系统上安全地假设一些事情:
yield
will give up the current timeslice and re-insert the thread into the scheduling queue. The amount of time that expires until the thread is executed again is usually entirely dependent upon the scheduler. Note that the Standard speaks of yield as an opportunity for rescheduling. So an implementation is completely free to return from a yield immediately if it desires. A yield will never mark a thread as inactive, so a thread spinning on a yield will always produce a 100% load on one core. If no other threads are ready, you are likely to lose at most the remainder of the current timeslice before you get scheduled again.sleep_*
will block the thread for at least the requested amount of time. An implementation may turn asleep_for(0)
into ayield
. Thesleep_for(1)
on the other hand will send your thread into suspension. Instead of going back to the scheduling queue, the thread goes to a different queue of sleeping threads first. Only after the requested amount of time has passed will the scheduler consider re-inserting the thread into the scheduling queue. The load produced by a small sleep will still be very high. If the requested sleep time is smaller than a system timeslice, you can expect that the thread will only skip one timeslice (that is, one yield to release the active timeslice and then skipping the one afterwards), which will still lead to a cpu load close or even equal to 100% on one core.
yield
将放弃当前时间片并将线程重新插入调度队列。线程再次执行之前的时间量通常完全取决于调度程序。请注意,标准将收益称为重新安排的机会。因此,如果需要,实现可以完全自由地立即从 yield 返回。yield 永远不会将线程标记为不活动,因此以 yield 旋转的线程将始终在一个核心上产生 100% 的负载。如果没有其他线程准备好,在再次安排之前,您可能最多会丢失当前时间片的剩余部分。sleep_*
将阻塞线程至少请求的时间量。一个实现可能会将 asleep_for(0)
变成 ayield
。在sleep_for(1)
另一方面,将发送您的线程进入暂停。线程不是回到调度队列,而是首先进入不同的休眠线程队列。只有在经过请求的时间量后,调度程序才会考虑将线程重新插入调度队列中。小睡眠产生的负荷还是会很高的。如果请求的睡眠时间小于系统时间片,可以预期线程只会跳过一个时间片(即一个yield释放活动时间片然后跳过一个),这仍然会导致cpu负载在一个核心上接近甚至等于 100%。
A few words about which is better for spin-locking. Spin-locking is a tool of choice when expecting little to no contention on the lock. If in the vast majority of cases you expect the lock to be available, spin-locks are a cheap and valuable solution. However, as soon as you do have contention, spin-locks willcost you. If you are worrying about whether yield or sleep is the better solution here spin-locks are the wrong tool for the job. You should use a mutex instead.
关于哪个更适合自旋锁定的几句话。当期望很少或没有锁争用时,自旋锁定是一种选择的工具。如果在绝大多数情况下您希望锁可用,则自旋锁是一种廉价且有价值的解决方案。但是,一旦您确实有争用,自旋锁就会花费您。如果您担心 yield 或 sleep 是否是更好的解决方案,那么自旋锁是该工作的错误工具。您应该改用互斥锁。
For a spin-lock, the case that you actually have to wait for the lock should be considered exceptional. Therefore it is perfectly fine to just yield here - it expresses the intent clearly and wasting CPU time should never be a concern in the first place.
对于自旋锁,您实际上必须等待锁的情况应该被视为例外。因此,在这里屈服是完全没问题的——它清楚地表达了意图,浪费 CPU 时间从一开始就不应该是一个问题。
回答by Rob L
I just did a test with Visual Studio 2013 on Windows 7, 2.8GHz Intel i7, default release mode optimizations.
我刚刚在 Windows 7、2.8GHz Intel i7、默认发布模式优化上使用 Visual Studio 2013 进行了测试。
sleep_for(nonzero) appears sleep for a minimium of around one millisecond and takes no CPU resources in a loop like:
sleep_for(nonzero) 出现最少约一毫秒的睡眠时间,并且在如下循环中不占用 CPU 资源:
for (int k = 0; k < 1000; ++k)
std::this_thread::sleep_for(std::chrono::nanoseconds(1));
This loop of 1,000 sleeps takes about 1 second if you use 1 nanosecond, 1 microsecond, or 1 millisecond. On the other hand, yield() takes about 0.25 microseconds each but will spin the CPU to 100% for the thread:
如果您使用 1 纳秒、1 微秒或 1 毫秒,则此 1,000 次睡眠循环大约需要 1 秒。另一方面,yield() 每个大约需要 0.25 微秒,但会将线程的 CPU 旋转到 100%:
for (int k = 0; k < 4,000,000; ++k) (commas added for clarity)
std::this_thread::yield();
std::this_thread::sleep_for((std::chrono::nanoseconds(0)) seems to be about the the same as yield() (test not shown here).
std::this_thread::sleep_for((std::chrono::nanoseconds(0)) 似乎与 yield() 大致相同(此处未显示测试)。
In comparison, locking an atomic_flag for a spinlock takes about 5 nanoseconds. This loop is 1 second:
相比之下,锁定一个自旋锁的 atomic_flag 大约需要 5 纳秒。这个循环是 1 秒:
std::atomic_flag f = ATOMIC_FLAG_INIT;
for (int k = 0; k < 200,000,000; ++k)
f.test_and_set();
Also, a mutex takes about 50 nanoseconds, 1 second for this loop:
此外,互斥锁大约需要 50 纳秒,此循环需要 1 秒:
for (int k = 0; k < 20,000,000; ++k)
std::lock_guard<std::mutex> lock(g_mutex);
Based on this, I probably wouldn't hesitate to put a yield in the spinlock, but I would almost certainly wouldn't use sleep_for. If you think your locks will be spinning a lot and are worried about cpu consumption, I would switch to std::mutex if that's practical in your application. Hopefully, the days of really bad performance on std::mutex in Windows are behind us.
基于此,我可能会毫不犹豫地在自旋锁中放置一个 yield,但我几乎肯定不会使用 sleep_for。如果您认为您的锁会旋转很多并且担心 CPU 消耗,如果这在您的应用程序中可行,我会切换到 std::mutex。希望 Windows 中 std::mutex 性能非常糟糕的日子已经过去了。
回答by Esavier
if you are interested in cpu load while using yield - it's very bad, except one case-(only your application is running, and you are aware that it will basically eat all your resources)
如果您在使用 yield 时对 CPU 负载感兴趣 - 这非常糟糕,除了一种情况 - (只有您的应用程序正在运行,并且您知道它基本上会吃掉您的所有资源)
here is more explanation:
这里有更多解释:
- running yield in loop will ensure that cpu will release execution of thread, still, if system try to come back to thread it will just repeat yield operation. This can make thread use full 100% load of cpu core.
- running
sleep()
orsleep_for()
is also a mistake, this will block thread execution but you will have something like wait time on cpu. Don't be mistaken, this IS working cpu but on lowest possible priority. While somehow working for simple usage examples ( fully loaded cpu on sleep() is half that bad as fully loaded working processor ), if you want to ensure application responsibility, you would like something like third example: combining! :
std::chrono::milliseconds duration(1); while (true) { if(!mutex.try_lock()) { std::this_thread::yield(); std::this_thread::sleep_for(duration); continue; } return; }
- 在循环中运行 yield 将确保 cpu 将释放线程的执行,但是,如果系统尝试返回线程,它只会重复 yield 操作。这可以使线程使用满 100% 的 cpu 内核负载。
- 运行
sleep()
或sleep_for()
也是一个错误,这将阻止线程执行,但您将在 CPU 上等待时间。不要误会,这是工作 CPU,但优先级最低。虽然以某种方式适用于简单的使用示例( sleep() 上的满载 CPU 是满载工作处理器的一半),但如果您想确保应用程序的责任,您需要类似于第三个示例的内容: 结合!:
std::chrono::milliseconds duration(1); while (true) { if(!mutex.try_lock()) { std::this_thread::yield(); std::this_thread::sleep_for(duration); continue; } return; }
something like this will ensure, cpu will yield as fast as this operation will be executed, and also sleep_for() will ensure that cpu will wait some time before even trying to execute next iteration. This time can be of course dynamicaly (or staticaly) adjusted to suits your needs
这样的事情将确保,cpu 将在执行此操作时尽可能快地产生,而且 sleep_for() 将确保 cpu 在尝试执行下一次迭代之前将等待一段时间。这个时间当然可以动态(或静态)调整以满足您的需要
cheers :)
欢呼:)
回答by shangjiaxuan
What you want is probably a condition variable. A condition variable with a conditional wake up function is typically implemented like what you are writing, with the sleep or yield inside the loop a wait on the condition.
你想要的可能是一个条件变量。具有条件唤醒函数的条件变量通常像您正在编写的那样实现,循环内的 sleep 或 yield 等待条件。
Your code would look like:
您的代码如下所示:
std::unique_lock<std::mutex> lck(mtx)
while(!bSomeCondition) {
cv.wait(lck);
}
Or
或者
std::unique_lock<std::mutex> lck(mtx)
cv.wait(lck, [bSomeCondition](){ return !bSomeCondition; })
All you need to do is notify the condition variable on another thread when the data is ready. However, you cannot avoid a lock there if you want to use condition variable.
您需要做的就是在数据准备好时通知另一个线程上的条件变量。但是,如果要使用条件变量,则无法避免锁定。