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
)并仍然询问are
go=1
SetEvent
可以重新排序。这是正式的文档吗..从您的链接中不知道以下同步功能使用适当的屏障来确保内存顺序:。。向同步对象发送信号的函数..等待函数,事实上,您不需要任何额外的代码来防止重新排序。另一个问题是正式证明