WakeByAddressAll 是如何排序的?
How is WakeByAddressAll ordered?
这是问题的后续
如果我直接使用 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;
}
现在让我们考虑将这个程序翻译成纯 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"。
因此,这些障碍阻止了所讨论的情况。
这是问题的后续
如果我直接使用 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;
}
现在让我们考虑将这个程序翻译成纯 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"。
因此,这些障碍阻止了所讨论的情况。