简体   繁体   English

C++11 线程等待行为:std::this_thread::yield() 与 std::this_thread::sleep_for( std::chrono::milliseconds(1) )

[英]C++11 Thread waiting behaviour: std::this_thread::yield() vs. std::this_thread::sleep_for( std::chrono::milliseconds(1) )

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-priority thread 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) ) ;但是,对于 C++11 线程库,关于std::this_thread::yield()std::this_thread::sleep_for( std::chrono::milliseconds(1) )的影响的文档并不多(至少我能找到) 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) ?第二个当然更冗长,但它们对于自旋锁是否同样有效,或者它是否遭受影响Sleep(0)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!
}

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. yield将放弃当前时间片并将线程重新插入调度队列。 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.因此,如果需要,实现可以完全自由地立即从 yield 返回。 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. yield 永远不会将线程标记为不活动,因此以 yield 旋转的线程将始终在一个核心上产生 100% 的负载。 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. sleep_*将至少在请求的时间内阻塞线程。 An implementation may turn a sleep_for(0) into a yield .一个实现可以将sleep_for(0)变成yield The sleep_for(1) on the other hand will send your thread into suspension.另一方面, sleep_for(1)将使您的线程暂停。 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释放活动时间片然后跳过一个),这仍然会导致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 will cost you.但是,一旦您确实有争用,自旋锁就会花费您。 If you are worrying about whether yield or sleep is the better solution here spin-locks are the wrong tool for the job .如果您担心 yield 或 sleep 是否是更好的解决方案,那么自旋锁是该工作的错误工具 You should use a mutex instead.您应该改用互斥锁。

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 时间从一开始就不应该是一个问题。

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.如果您使用 1 纳秒、1 微秒或 1 毫秒,则此 1,000 次睡眠循环大约需要 1 秒。 On the other hand, yield() takes about 0.25 microseconds each but will spin the CPU to 100% for the thread:另一方面,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.相比之下,锁定一个自旋锁的 atomic_flag 大约需要 5 纳秒。 This loop is 1 second:这个循环是 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.基于此,我可能会毫不犹豫地在自旋锁中放置一个 yield,但我几乎肯定不会使用 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.如果您认为您的锁会旋转很多并且担心 CPU 消耗,如果这在您的应用程序中可行,我会切换到 std::mutex。 Hopefully, the days of really bad performance on std::mutex in Windows are behind us.希望 Windows 中 std::mutex 性能非常糟糕的日子已经过去了。

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.在循环中运行 yield 将确保 cpu 将释放线程的执行,但是,如果系统尝试返回线程,它只会重复 yield 操作。 This can make thread use full 100% load of cpu core.这可以使线程使用满 100% 的 cpu 内核负载。
  • running sleep() or sleep_for() is also a mistake, this will block thread execution but you will have something like wait time on cpu.运行sleep()sleep_for()也是一个错误,这会阻止线程执行,但您会在 CPU 上等待时间。 Don't be mistaken, this IS working cpu but on lowest possible priority.不要误会,这是工作 CPU,但优先级最低。 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:虽然以某种方式适用于简单的使用示例( sleep() 上的满载 cpu 是满载工作处理器的一半),但如果您想确保应用程序的责任,您会喜欢第三个示例:
  • combining!结合! :

     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.这样的事情将确保,cpu 将在执行此操作时尽可能快地产生,而且 sleep_for() 将确保 cpu 在尝试执行下一次迭代之前将等待一段时间。 This time can be of course dynamicaly (or staticaly) adjusted to suits your needs这个时间当然可以动态(或静态)调整以满足您的需要

cheers :)欢呼:)

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.但是,如果要使用条件变量,则无法避免锁定。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM