WakeByAddressAll 是如何排序的?

How is WakeByAddressAll ordered?

这是问题的后续

如果我直接使用 WaitOnAddressfutex,该问题的答案是什么。


从该问题的答案来看,结论是下面的程序没有挂起,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;
}

现在让我们考虑将这个程序翻译成纯 Windows API,没有 C++20 甚至 C++11:

#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;
}

由于虚假唤醒,我添加了循环。

同样的问题。如果在 (1) 中观察到 (2) 和 (3) 的顺序相反,则可能会由于丢失通知而挂起。 WinAPI 会阻止这种情况,还是需要明确设置围栏。


这个问题的答案的实际应用是 std::atomic<T>::wait 由标准库或其替代品在 Windows 平台上的实现。

在 Linux 平台实施的背景下,我对 futex 也有同样的问题。

来自 Windows 文档的 Synchronization and Multiprocessor Issues 页:

Memory Ordering

[...]

The following synchronization functions use the appropriate barriers to ensure memory ordering:

  • Functions that enter or leave critical sections
  • Functions that signal synchronization objects
  • Wait functions
  • Interlocked functions

Synchronization Functions page, Wait functions section lists WaitOnAddress, WakeByAddressAll, WakeByAddressSingle. They are also listed in Wait Functions 页面上 等待地址

所以这三个函数都算作等待函数,因此有适当的障碍。虽然没有定义 适当 障碍到底是什么,但似乎无法想象任何障碍都不会阻止问题中的情况,否则会以某种方式 "appropriate"。

因此,这些障碍阻止了所讨论的情况。