C++11线程等待行为:std::this_Thread::yield()与std::this_Thread::sleep_for(std::chrono::毫秒(1))

当我编写微软特定的C++代码时,写睡眠(1)比代码>睡眠(0)要更有效,因为代码睡眠(0)会占用更多的CPU时间,而且,如果有另一个相等优先级线程等待运行,它只能产生。

但是,对于C++11线程库,关于std::this_thread::yield()与std::this_thread::sleep_for(std::chrono::millides(1))的效果没有太多文档(至少我能够找到);第二种方法当然更为详细,但它们对自旋锁是否同样有效,或者它是否受到影响Sleep(0)Sleep(1)的潜在相同问题的影响

一个示例循环,其中(std::this_thread::yield()或(std::this_thread::sleep_for(std::chrono::millides(1))可以接受:

无效自旋锁(常量布尔和bSomeCondition)
{
//等待某些条件得到满足
而(!bSomeCondition)
{
/*std::this_thread::yield()或
std::this_线程::sleep_for(std::chrono::毫秒(1))
这是可以接受的*/
}
//做点什么!
}

这里的标准有些模糊,因为具体的实现很大程度上会受到底层操作系统的调度能力的影响

也就是说,在任何现代操作系统上,您都可以安全地假设一些事情:

  • yield将放弃当前时间片,并将线程重新插入调度队列。直到线程再次执行的过期时间通常完全取决于调度程序。请注意,该标准将收益率称为重新安排时间的机会。因此,一个实现完全可以自由地从收益中立即返回,如果它愿意的话。产量永远不会将线程标记为非活动线程,因此在产量上旋转的线程始终会在一个内核上产生100%的负载。如果没有其他线程准备就绪,则在重新安排之前,可能会丢失最多当前时间片的剩余部分
  • sleep.*将至少在请求的时间内阻塞线程。一个实现可以将(0)sleep\u转换为yield。另一方面,(1)sleep\u将使线程暂停。线程不会返回调度队列,而是首先进入另一个休眠线程队列。只有在请求的时间量过去之后,调度器才会考虑重新插入线程到调度队列中。小规模睡眠产生的负荷仍然很高。如果请求的睡眠时间小于系统时间片,则可以预期线程将只跳过一个时间片(即,一个线程将释放活动时间片,然后跳过该时间片),这仍将导致一个内核上的cpu负载接近或甚至等于100%

关于哪一个更适合旋转锁定,请讲几句。当期望锁上几乎没有争用或没有争用时,自旋锁是一种选择工具。如果在绝大多数情况下您希望锁可用,那么旋转锁是一种廉价且有价值的解决方案。然而,一旦您有了争用,自旋锁将花费您的成本。如果您担心是屈服还是睡眠是更好的解决方案,那么旋转锁是工作的错误工具。您应该使用互斥锁

对于旋转锁,您实际上必须等待锁定的情况应视为例外。因此,在这里让步是完全可以的——它清楚地表达了意图,浪费CPU时间从一开始就不应该成为一个问题

发表评论