0

这是问题如何 std::atomic<T>::notify_all 排序?

如果我使用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 平台上由标准库或替代标准库实现。

futex在 Linux 平台实现的上下文中,我也有同样的问题。

4

1 回答 1

1

从Windows 文档的同步和多处理器问题页面:

内存排序

[...]

以下同步函数使用适当的屏障来确保内存排序:

  • 进入或离开临界区的函数
  • 信号同步对象的函数
  • 等待函数
  • 联锁功能

同步功能页面上,等待功能部分列出WaitOnAddressWakeByAddressAllWakeByAddressSingle。它们还在等待功能页面中列为等待地址

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

因此,这些障碍阻止了有问题的情况。

于 2020-06-11T19:52:55.693 回答