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

问题描述 投票:36回答:4

[有人告诉我在编写Microsoft特定的C ++代码时,编写Sleep(1)的效果比Sleep(1)好得多,因为Sleep(0)会占用更多的CPU时间,而且只有在有另一个equor-priority线程正在等待运行。

但是,对于C ++ 11线程库,关于Sleep(0)std::this_thread::yield()的效果的文档很少(至少我已经找到了);第二个当然更冗长,但是它们对于自旋锁是否都同样有效,还是它可能受到影响std::this_thread::sleep_for( std::chrono::milliseconds(1) )Sleep(0)的相同陷阱的困扰?

Sleep(1)std::this_thread::yield()可接受的示例循环:

std::this_thread::sleep_for( std::chrono::milliseconds(1) )
c++ multithreading c++11 standard-library
4个回答
33
投票

此标准在某种程度上是模糊的,因为具体的实现将在很大程度上受到底层操作系统的调度能力的影响。

话虽如此,您可以在任何现代OS上放心地承担一些事情:

  • 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! } 将放弃当前时间片,并将线程重新插入调度队列中。直到再次执行线程为止的时间长短通常完全取决于调度程序。请注意,该标准将产量称为重新安排的机会。因此,如果需要,实现完全可以立即从收益中自由返回。良率永远不会将线程标记为非活动状态,因此按良率旋转的线程将始终在一个内核上产生100%的负载。如果没有其他线程准备就绪,则在重新安排之前,您很可能会丢失当前时间片的其余部分。
  • yield将阻塞线程至少所请求的时间。一种实现方式可以将sleep_*转换为sleep_for(0)。另一方面,yield将使您的线程暂停。线程不返回调度队列,而是先进入睡眠线程的其他队列。仅在请求的时间量过去之后,调度程序才会考虑将线程重新插入调度队列中。小睡产生的负担仍然很高。如果请求的睡眠时间小于系统时间片,则可以预期线程将仅跳过一个时间片(即,释放一个产量以释放活动时间片,然后再跳过一个时间片),这仍将导致cpu负载一个核心接近甚至等于100%。

关于哪种方法更适合自旋锁定。当期望很少或没有争用锁时,自旋锁定是一种选择的工具。如果在大多数情况下您希望该锁可用,那么自旋锁是一种廉价且有价值的解决方案。但是,一旦您确实有争用,自旋锁will就会使您付出代价。如果您担心屈服还是睡眠是更好的解决方案,那么自旋锁就是错误的工作工具。您应该改用互斥锁。

对于自旋锁,您实际上必须等待锁的情况应视为例外。因此,仅在此处屈服就可以了-它清楚地表达了意图,而浪费CPU时间首先不应该成为问题。


15
投票

我刚刚在Windows 7、2.8GHz Intel i7,默认发布模式优化下使用Visual Studio 2013进行了测试。

sleep_for(nonzero)出现至少一毫秒的睡眠,并且在类似这样的循环中不占用任何CPU资源:

sleep_for(1)

如果使用1纳秒,1微秒或1毫秒,则此1,000次睡眠的循环大约需要1秒钟。另一方面,yield()大约需要0.25微秒,但是对于线程来说,它将使CPU旋转到100%:

for (int k = 0; k < 1000; ++k)
    std::this_thread::sleep_for(std::chrono::nanoseconds(1));

std :: this_thread :: sleep_for((std :: chrono :: nanoseconds(0))似乎与yield()差不多(此处未显示测试)。

相比之下,为spinlock锁定atomic_flag大约需要5纳秒。该循环为1秒:

for (int k = 0; k < 4,000,000; ++k) (commas added for clarity)
    std::this_thread::yield();

此外,互斥体大约需要50纳秒,此循环需要1秒:

std::atomic_flag f = ATOMIC_FLAG_INIT;
for (int k = 0; k < 200,000,000; ++k)
    f.test_and_set();

基于此,我可能会毫不犹豫地将收益率置于自旋锁中,但几乎可以肯定我不会使用sleep_for。如果您认为锁会旋转很多并且担心cpu消耗,那么如果您的应用程序可行,我将切换到std :: mutex。希望,在Windows中std :: mutex上真正糟糕的性能的日子已经过去。


4
投票

如果您对使用yield时的cpu加载感兴趣-除一种情况外,这非常糟糕-(仅您的应用程序正在运行,并且您知道它将基本上耗尽所有资源)

这里有更多说明:

  • 在循环中运行yield将确保cpu将释放线程的执行,但是,如果系统尝试返回线程,它将仅重复yield操作。这可以使线程完全使用100%的cpu内核负载。
  • 运行for (int k = 0; k < 20,000,000; ++k) std::lock_guard<std::mutex> lock(g_mutex); sleep()也是一个错误,这将阻止线程执行,但是您会在CPU上遇到等待时间之类的东西。别误会,这是可以正常工作的CPU,但是优先级最低。尽管以某种方式为简单的用法示例工作(sleep()上的满载cpu的性能是满载工作的处理器的一半),但如果要确保应用程序的责任,您可能需要第三个示例:
  • 合并! :

    sleep_for()

这样的操作将确保,cpu将产生与执行该操作一样快的速度,sleep_for()将确保cpu将等待一段时间,甚至尝试执行下一次迭代。当然,可以根据您的需求动态(或静态)调整此时间。

欢呼:)


1
投票

您想要的可能是条件变量。具有条件唤醒功能的条件变量通常像您正在编写的那样实现,让睡眠或yield循环内等待条件。

您的代码如下:

std::chrono::milliseconds duration(1);
while (true)
   {
      if(!mutex.try_lock())
      {
           std::this_thread::yield();
           std::this_thread::sleep_for(duration);
           continue;
      }
      return;
   }

std::unique_lock<std::mutex> lck(mtx)
while(!bSomeCondition) {
    cv.wait(lck);
}

您需要做的就是在数据准备就绪时在另一个线程上通知条件变量。但是,如果要使用条件变量,则无法避免在那里锁定。

© www.soinside.com 2019 - 2024. All rights reserved.