下面的代码使用QueueUserAPC
函数向调度线程添加命令,以便同步控制台输出。
#include <Windows.h>
#include <iostream>
constexpr auto fenceName = L"GlobalFence";
constexpr auto dispatchCloser = L"GlobalDispatchStop";
constexpr int threadCount = 5;
DWORD WINAPI pure(LPVOID lpThreadParameter)
{
const HANDLE dispatchCloseEvent = OpenEventW(EVENT_ALL_ACCESS, FALSE, dispatchCloser);
while(WaitForSingleObjectEx(dispatchCloseEvent, INFINITE, TRUE) != WAIT_IO_COMPLETION)continue;
return 0;
}
HANDLE dispatcher;
int main()
{
const HANDLE dispatchCloseEvent = CreateEventW(nullptr, TRUE, FALSE, dispatchCloser);
dispatcher = CreateThread(NULL, 1024, &pure, 0, 0, NULL);
const HANDLE g_FenceEvent = CreateEventW(nullptr, TRUE, FALSE, fenceName);
HANDLE threads[threadCount];
for (int i = 0; i < threadCount; i++)
{
threads[i] = CreateThread(NULL, 1024*1024,
[](LPVOID) -> DWORD
{
DWORD d = QueueUserAPC(([](ULONG_PTR) {std::cout << "thread openedn"; }), dispatcher, NULL);
if(d == 0)std::cout << GetLastError() << std::endl;
HANDLE a = OpenEventW(EVENT_ALL_ACCESS, FALSE, fenceName);
WaitForSingleObject(a, INFINITE);
d = QueueUserAPC([](ULONG_PTR) {std::cout << "thread releasedn"; }, dispatcher, NULL);
if (d == 0)std::cout << GetLastError() << std::endl;//often reports error 31
return 0;
},
0, 0, NULL);
}
Beep(300, 300);//the length of the delay effects the behavior, somehow.
SetEvent(g_FenceEvent);
SetEvent(dispatchCloseEvent);
WaitForMultipleObjects(threadCount, threads, TRUE, INFINITE);
WaitForSingleObject(dispatcher, INFINITE);
SetEvent(dispatchCloseEvent);
for (int i = 0; i < threadCount; i++)
CloseHandle(threads[i]);
CloseHandle(g_FenceEvent);
CloseHandle(dispatchCloseEvent);
}
代码的正确执行率约为40%。有时(虽然这种情况比较少见),"线程打开了"。文本不会被写入控制台正确的次数,但没有错误报告从getLastError()
当pure()
中的循环收到第一个APC通知时,循环中断,pure()
退出,线程终止。
错误31是ERROR_GEN_FAILURE
,根据QueueUserAPC()
文档:
当线程处于被终止的过程中,调用QueueUserAPC来添加线程的APC队列将失败,返回(31)ERROR_GEN_FAILURE。
如果你希望调度线程处理多个APC通知,它需要保持运行。您的意思是在循环条件中使用==
而不是!=
:
while (WaitForSingleObjectEx(dispatchCloseEvent, INFINITE, TRUE) == WAIT_IO_COMPLETION) continue;
这样,如果等待由于排队的APC而退出,循环将返回等待下一个APC。循环将中断,退出pure()
终止线程,只有当它收到返回值而不是APC通知时,例如当关闭事件发出信号时WAIT_OBJECT_0
。
我看到的另一个问题是,你过早地给dispatchCloseEvent
发信号,所以dispatcher线程可以停止运行,而fenced线程仍然试图将apc排队到它。这就是为什么在每个隔离线程中对QueueUserAPC()
的第二次调用随机失败的原因。您需要先等待所有被隔离的线程完成,然后向调度程序发出停止运行的信号。
SetEvent(g_FenceEvent);
WaitForMultipleObjects(threadCount, threads, TRUE, INFINITE);
SetEvent(dispatchCloseEvent); // <-- move here!
WaitForSingleObject(dispatcher, INFINITE);
同样,你所有的线程都泄漏了OpenEventW()
打开的HANDLE
。你需要调用CloseHandle()
,根据OpenEventW()
文档:
使用CloseHandle函数关闭句柄。当进程终止时,系统自动关闭句柄。事件对象在其最后一个句柄关闭时被销毁。
就此而言,您根本不需要OpenEventW()
。您可以通过LPVOID
参数将现有的HANDLE
从main()
传递给每个线程:
DWORD WINAPI pure(LPVOID lpThreadParameter)
{
HANDLE dispatchCloseEvent = (HANDLE) lpThreadParameter;
...
return 0;
}
CreateThread(..., &pure, dispatchCloseEvent, ...);
CreateThread(...,
[](LPVOID param) -> DWORD
{
HANDLE a = (HANDLE) param;
...
},
g_fenceEvent, ...);
或者直接使用全局变量。
无论哪种方式,消除OpenEventW()
后,在调用CreateEventW()
时不再需要为事件分配名称,从而不再将它们暴露于外部代码的潜在干扰。
此外,您也没有关闭dispatcher
线程的HANDLE
,就像您正在关闭围栏线程的HANDLE
s一样。