C++11线程等待行为:std::this_Thread::yield()与std::this _Thread::sle
C++11 Thread waiting behaviour: std::this_thread::yield() vs. std::this_thread::sleep_for( std::chrono::milliseconds(1) )
在编写特定于Microsoft的C++代码时,有人告诉我,编写Sleep(1)
比Sleep(0)
在自旋锁定方面要好得多,因为Sleep(0)
将使用更多的CPU时间,而且,只有当有另一个等优先级线程等待运行时,它才会产生效果。
然而,对于C++11线程库,没有太多关于std::this_thread::yield()
与std::this_thread::sleep_for( std::chrono::milliseconds(1) )
的影响的文档(至少我能找到);第二个当然更详细,但它们对自旋锁来说是否同样有效,或者它是否受到影响Sleep(0)
和Sleep(1)
的潜在相同gotchas的影响?
可以接受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!
}
这里的标准有些模糊,因为具体实现将在很大程度上受到底层操作系统的调度能力的影响。
话虽如此,你可以放心地在任何现代操作系统上假设一些事情:
- CCD_ 10将放弃当前时间片并将线程重新插入到调度队列中。线程再次执行之前到期的时间量通常完全取决于调度程序。请注意,标准将收益率称为重新安排的机会。因此,如果一个实现想要的话,它可以完全自由地立即从收益中返回。产量永远不会将线程标记为无效,因此在产量上旋转的线程将始终在一个核心上产生100%的负载。如果没有其他线程准备好,那么在重新安排之前,您可能最多会损失当前时间片的剩余时间
- CCD_ 11将阻塞线程至少所请求的时间量。一种实现方式可以将
sleep_for(0)
转变为yield
。另一方面,sleep_for(1)
将使您的线程处于挂起状态。线程不是返回到调度队列,而是先转到另一个睡眠线程队列。只有在经过了请求的时间后,调度程序才会考虑将线程重新插入到调度队列中。小睡眠产生的负荷仍然很高。如果请求的睡眠时间小于系统时间片,则可以预期线程将只跳过一个时间片(即,一次释放活动时间片,然后跳过该时间片),这仍然会导致一个内核上的cpu负载接近甚至等于100%
关于哪种更适合旋转锁定,请简单介绍。当期望锁上几乎没有争用时,旋转锁定是一种选择工具。如果在绝大多数情况下,你希望锁是可用的,那么旋转锁是一个廉价而有价值的解决方案。但是,一旦发生争用,旋转锁就会让您付出的代价。如果你担心产量还是睡眠是更好的解决方案,那么旋转锁是错误的工具。您应该使用互斥。
对于旋转锁,您实际上必须等待锁的情况应该被视为例外。因此,在这里屈服是完全可以的——它清楚地表达了意图,浪费CPU时间从一开始就不应该是一个问题。
我刚刚在Windows 7、2.8GHz Intel i7、默认发布模式优化上用Visual Studio 2013进行了一次测试。
sleep_for(非零)显示睡眠的最小时间约为1毫秒,并且在循环中不占用CPU资源,如:
for (int k = 0; k < 1000; ++k)
std::this_thread::sleep_for(std::chrono::nanoseconds(1));
如果使用1纳秒、1微秒或1毫秒,1000次睡眠的循环大约需要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::纳秒(0))似乎与yield()大致相同(此处未显示测试)。
相比之下,为spinlock锁定atomic_flag大约需要5纳秒。此循环为1秒:
std::atomic_flag f = ATOMIC_FLAG_INIT;
for (int k = 0; k < 200,000,000; ++k)
f.test_and_set();
另外,一个互斥锁大约需要50纳秒,对于这个循环是1秒:
for (int k = 0; k < 20,000,000; ++k)
std::lock_guard<std::mutex> lock(g_mutex);
基于此,我可能会毫不犹豫地在spinlock中加入yield,但我几乎肯定不会使用sleep_for。如果你认为你的锁会旋转很多,并且担心cpu消耗,如果在你的应用程序中可行的话,我会切换到std::mutex。希望Windows中std::mutex性能非常糟糕的日子已经过去了。
如果你在使用yield时对cpu负载感兴趣,那就很糟糕了,只有一种情况除外(只有你的应用程序在运行,你知道它基本上会吃掉你所有的资源)
这里有更多的解释:
- 在循环中运行yield将确保cpu将释放线程的执行,但是,如果系统试图返回线程,它只会重复yield操作。这可以使线程使用cpu核心的100%负载
- 运行
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; }
类似这样的操作将确保cpu的产量与执行此操作的速度一样快,而且sleep_for()将确保cpu在尝试执行下一次迭代之前等待一段时间。这个时间当然可以动态(或静态)调整,以满足您的需求
cheers:)
您想要的可能是一个条件变量。带有条件唤醒函数的条件变量通常像您正在编写的那样实现,其中sleep或yield在循环中等待条件。
你的代码看起来像:
std::unique_lock<std::mutex> lck(mtx)
while(!bSomeCondition) {
cv.wait(lck);
}
或
std::unique_lock<std::mutex> lck(mtx)
cv.wait(lck, [bSomeCondition](){ return !bSomeCondition; })
您所需要做的就是在数据准备就绪时通知另一个线程上的条件变量。但是,如果要使用条件变量,则无法避免在那里锁定。
- 在std::thread中,joinable()然后join()线程安全吗
- 分离一个静态常量 std::thread?
- 当指向对象的指针作为参数传递给 std::thread 时,内存可见性
- 如何从 std::thread 返回值
- 将 std::thread by 值推送到列表中
- 转发变量参数列表以模拟 std::thread
- 对 'std::thread::_M_start_thread CMake 的未定义引用进行基准测试
- std::thread 增加 DLL 引用计数,从而防止卸载 DLL
- 如何防止 std::thread 在 QT 中冻结 GUI?
- 对带有唯一指针的 std::thread 使用类成员函数时出现编译错误
- 为什么参数在构造 std::thread 时移动两次
- std::thread::_Invoker 使用线程编程时出错
- 在线程 A 中创建一个 std::thread 对象,在线程 B 中连接
- 为什么编译器抱怨 std::thread 参数在转换为右值后必须是可调用的?
- ZeroMQ 在使用 std::thread 创建工作线程时崩溃
- 在没有复制构造函数的对象的成员函数中启动 std::thread
- CLang:在 std::thread 中运行函数会导致结构创建BAD_ACCESS
- 调用以CWinThread为基的类运算符()的std::thread失败
- 如何通过std::thread生成多个线程
- std::future::get()或std::future::wait()是std::thread::join()的替