C++ WakeByAddressAll是如何排序的?
这是问题的后续行动 如果我直接使用C++ WakeByAddressAll是如何排序的?,c++,winapi,memory-barriers,futex,C++,Winapi,Memory Barriers,Futex,这是问题的后续行动 如果我直接使用WaitOnAddress或futex,这个问题的答案是什么 从该问题的答案来看,以下的程序不存在,C++提供了必要的保证,虽然措辞仍然会引起问题: #include <atomic> #include <chrono> #include <thread> int main() { std::atomic<bool> go{ false }; std::thread thd([&go]
WaitOnAddress
或futex
,这个问题的答案是什么
从该问题的答案来看,以下的程序不存在,C++提供了必要的保证,虽然措辞仍然会引起问题:
#include <atomic>
#include <chrono>
#include <thread>
int main()
{
std::atomic<bool> go{ false };
std::thread thd([&go] {
go.wait(false, std::memory_order_relaxed); // (1)
});
std::this_thread::sleep_for(std::chrono::milliseconds(400));
go.store(true, std::memory_order_relaxed); // (2)
go.notify_all(); // (3)
thd.join();
return 0;
}
#包括
#包括
#包括
int main()
{
std::原子go{false};
标准::螺纹螺纹总长度([&go]{
go.wait(false,std::memory_order_released);/(1)
});
std::this_线程::sleep_for(std::chrono::毫秒(400));
go.store(true,std::memory_order_released);/(2)
go.notify_all();/(3)
thd.join();
返回0;
}
现在我们考虑把这个程序翻译成纯Windows API,没有C++ 20,甚至C++ 11:< /p>
#include <Windows.h>
#pragma comment(lib, "Synchronization.lib")
volatile DWORD go = 0;
DWORD CALLBACK ThreadProc(LPVOID)
{
DWORD zero = 0;
while (go == zero)
{
WaitOnAddress(&go, &zero, sizeof(DWORD), INFINITE); // (1)
}
return 0;
}
int main()
{
HANDLE thread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
if (thread == 0)
{
return 1;
}
Sleep(400);
go = 1; // (2)
WakeByAddressAll(&go); // (3)
WaitForSingleObject(thread, INFINITE);
CloseHandle(thread);
return 0;
}
#包括
#pragma注释(lib,“Synchronization.lib”)
易失性DWORD go=0;
DWORD回调线程进程(LPVOID)
{
DWORD零=0;
while(go==零)
{
WaitOnAddress(&go,&zero,sizeof(DWORD),无限);/(1)
}
返回0;
}
int main()
{
HANDLE thread=CreateThread(NULL,0,ThreadProc,NULL,0,NULL);
如果(线程==0)
{
返回1;
}
睡眠(400);
go=1;/(2)
WakeByAddressAll(&go);/(3)
WaitForSingleObject(线程,无限);
闭柄(螺纹);
返回0;
}
由于虚假的唤醒,我添加了循环
同样的问题。如果(1)中的(2)和(3)顺序相反,则可能由于通知丢失而挂起。WinAPI是否阻止了这一点,或者是否需要明确地设置围栏
这个问题的答案的实际应用是通过标准库或Windows平台上的替代库实现
std::atomic::wait
在Linux平台实施的背景下,我对futex也有同样的问题。来自Windows文档的第页:
内存排序
[……]
以下同步功能使用适当的屏障来确保内存顺序:
- 进入或离开关键部分的函数
- 向同步对象发送信号的函数
- 等待功能
- 互锁功能
WaitOnAddress
,WakeByAddressAll
,WakeByAddressSingle
。他们也被列在页面中作为等待地址
因此,这三个函数都算作等待函数,因此具有适当的屏障。虽然没有明确界定什么是适当的障碍,但似乎不可能想象出任何障碍不会阻止问题中的情况,但在其他情况下,它在某种程度上是“适当的”
因此,这些障碍阻止了这种情况的发生。您可能已经看到了这一点,但请确保在谷歌上搜索“wakebyaddress oldnewthing”。陈雷蒙(Raymond Chen)有一系列关于该设施使用情况的帖子(例如,this)。@Christian.K,我不久前看到过这些帖子,不过还是谢谢你的提醒。您链接的那个包含一个有趣的提示,提醒您不要获取未记录的实现细节。这就是我问这个问题的原因。基本上,调试器显示函数确实具有所需的所有防护,因此程序不会挂起,但我正在考虑避免依赖此观察结果,如果它在将来或其他平台上可能被破坏;任何
WakeByAddressAll(&go)
可能做的事情都会涉及到内核中某个地方的存储,该存储由其他内核上的负载读取,因此(通过x86的强内存模型)创建释放/获取同步。其他ISA上的任何合理设计都同样涉及核心之间的通信,而且可能不仅仅是mou消费
。该标准措辞中可能存在的弱点是,它允许假设的DeathStation 9000实现,而不是现实生活中的实现可能存在此问题。事实上,所有windows同步api都作为完整内存屏障工作。例如,您可以使用SetEvent
代替WakeByAddressAll
(和WaitForSingleObject
代替WaitOnAddress
)并仍然询问arego=1代码>和SetEvent
可以重新排序。这是正式的文档吗..从您的链接中不知道以下同步功能使用适当的屏障来确保内存顺序:。。向同步对象发送信号的函数..等待函数,事实上,您不需要任何额外的代码来防止重新排序。另一个问题是正式证明