在 Windows 中等待 std::atomic<int> 的正确方法?
Posted
技术标签:
【中文标题】在 Windows 中等待 std::atomic<int> 的正确方法?【英文标题】:Correct way to wait on a std::atomic<int> in Windows? 【发布时间】:2015-04-29 12:29:00 【问题描述】:以下代码可以运行,但是有问题:
#include <atomic>
#include "windows.h"
std::atomic<int> foo;
DWORD WINAPI baz(void *) Sleep(10000); foo.store(1); return 0;
int main()
foo.store(0);
HANDLE h = CreateThread(NULL, 0, baz, NULL, 0, NULL);
while ( !foo.load() )
Sleep(0);
WaitForSingleObject(h, INFINITE);
CloseHandle(h);
return 0;
程序在等待时使用最大 CPU。
如果我将 Sleep(0);
更改为 Sleep(1);
则它使用 0% 的 CPU,但是我担心一些事情:
load()
,这可能仍会消耗比必要更多的系统资源。
有没有更好的方法?
背景:我有一些代码正在使用 Win32 事件来唤醒线程,使用 WaitForMultipleObjects
,但我想知道是否可以使用 std::atomic
标志代替,与目的可能是使代码更简单、更快和/或更便携。 IDK 操作系统如何实现 WaitForSingleObject 和 WaitForMultipleObjects,例如无论是在内部使用Sleep(1)
,还是有一些更智能的技术可用。
注意:atomic<int>
是无锁的;为循环生成的程序集是:
movq __imp_Sleep(%rip), %rbx
movq %rax, %rsi
jmp .L4
.p2align 4,,10
.L5:
xorl %ecx, %ecx
call *%rbx
.L4:
movl foo(%rip), %edx
testl %edx, %edx
je .L5
【问题讨论】:
仅供参考,如果另一个线程正在等待运行,sleep(0) 只会导致重新调度。如果没有其他线程在等待切片,则立即返回。Sleep(0)
本质上是一个忙等待。即使您指定一个大于 0 的值,您仍在轮询与等待的相反。 atomic
不等同于事件。至于可移植性,您可能不得不等待 C++ 17、tasks 和 futures。同时你可以使用Boost's futures
【参考方案1】:
您不应该等待std::atomic
,它们不是为此而设计的。如果您想要一个不忙的等待,那么您想要一个std::condition_variable
。
std::condition_variable
专门设计为能够在不使用任何 CPU 的情况下等待直到收到信号并立即唤醒。
它们的用法有点冗长,您需要将它们与互斥体结合起来,但是一旦您习惯了它们,它们就会变得强大:
#include <condition_variable>
#include <mutex>
#include <thread>
std::condition_variable cv;
std::mutex lock;
int foo;
void baz()
std::this_thread::sleep_for(std::chrono::seconds(10));
auto ul = std::unique_lock<std::mutex>(lock);
foo = 1;
cv.notify_one();
int main()
foo = 0;
auto thread = std::thread(baz);
auto ul = std::unique_lock<std::mutex>(lock);
cv.wait(ul, []()return foo != 0;);
thread.join();
return 0;
【讨论】:
感谢您的回答。但是我不能使用它,因为我使用 g++ for Windows (mingw-w64),它没有std::condition_variable
。对于支持它的编译器,您知道此代码将解析为哪个 Windows API 构造吗?
@Matt McNabb Windows 确实有自己的CONDITION_VARIABLE,如果你不能使用 C++ 的话。另一种选择是使用自动重置unnamed events。
刚刚发现this addon for mingw 给出了std::thread
和std::condition_variable
“它们不是为此而设计的。” ... C++20 添加了std::atomic<T>::wait
。从那时起,最新的原子就是为此而设计的。如果我无法将 atomic 更改为其他内容但仍需要在 C++20 之前等待怎么办?【参考方案2】:
如果您只想发出完成信号,使用标准库您有两种解决方案 - condition_variable
和 future
。由于Mike Vine已经提供了使用前者的解决方案,我将展示如何使用后者:
#include <future>
#include <thread>
#include <iostream>
void baz(std::promise<int>& pr)
std::this_thread::sleep_for(std::chrono::seconds(10));
pr.set_value(1); // fulfill the prmoise
int main()
std::promise<int> promise;
auto future = promise.get_future();
auto thread = std::thread(baz, std::ref(promise));
int foo = future.get(); // blocks until promise is fulfilled
std::cout << "Thread yielded: " << foo << std::endl;
thread.join();
return 0;
请注意,condition_variable
可能会被通知并等待多次,但promise
只能完成一次。
【讨论】:
【参考方案3】:从 C++20 开始,您可以使用std::atomic<T>::wait(T old)
。它的工作原理类似于条件变量,并且不会自旋。
要使用它,您可以使用原子的旧(当前)值调用此方法,它会阻塞,直到原子的值更改为不同的值。 (请注意,ABA 问题仍然存在——可能会遗漏对其他值的一些简短更改。)
当值改变时,其他线程可以通过调用std::atomic<T>::notify_one()
或std::atomic<T>::notify_all()
知道这一点。因此,您的示例将是:
#include <atomic>
#include "windows.h"
std::atomic<int> foo;
DWORD WINAPI baz(void *) Sleep(10000); foo.store(1); foo.notify_one(); return 0;
int main()
foo.store(0);
HANDLE h = CreateThread(NULL, 0, baz, NULL, 0, NULL);
foo.wait(0);
WaitForSingleObject(h, INFINITE);
CloseHandle(h);
return 0;
【讨论】:
【参考方案4】:检查https://msdn.microsoft.com/en-us/library/windows/desktop/ms686298%28v=vs.85%29.aspx,上面写着
dwMilliseconds [in]
暂停执行的时间间隔,以毫秒为单位。
零值会导致线程将其时间片的剩余部分交给任何其他准备运行的线程。如果没有其他线程准备运行,则函数立即返回,线程继续执行。
由于您看到程序在等待时使用了最大 CPU,这意味着 Sleep(0)
立即返回,因此会消耗您的 CPU 时间。
这里的问题似乎与std::atomic<>
完全无关。
【讨论】:
以上是关于在 Windows 中等待 std::atomic<int> 的正确方法?的主要内容,如果未能解决你的问题,请参考以下文章
互锁变量访问(在布尔值上)和 std::atomic_flag 之间的区别
在实践中,C++11 中 std::atomic 的内存占用是多少?
C++ error: use of deleted function ‘std::atomic<short unsigned int>::atomic(const std::atomic<short