[英]How to implement communication between threads using WinAPI?
I am trying to use WinAPI events to communicate between threads.我正在尝试使用 WinAPI 事件在线程之间进行通信。 I have one main thread and 2 "worker" threads, which get requests for work and report when done.
我有一个主线程和 2 个“工作者”线程,它们接收工作请求并在完成后报告。 For simplicity, main thread always sends requests to a specific worker thread;
为简单起见,主线程总是向特定的工作线程发送请求; I use dedicated events for that.
我为此使用专门的事件。
#include <stdio.h>
#include <inttypes.h>
#include "windows.h"
#include "process.h"
#define NUM_THREADS 2
struct mywork
{
int thread_index;
HANDLE event_do;
HANDLE event_done;
};
struct mywork gg_work[NUM_THREADS];
uintptr_t gg_threads[NUM_THREADS];
void my_iterate(void* lpParam)
{
while (1)
{
struct mywork* pwork = (struct mywork*)lpParam;
printf("Worker %d: wait for request\n", pwork->thread_index);
WaitForMultipleObjects(1, &pwork->event_do, TRUE, INFINITE);
printf("Worker %d: working\n", pwork->thread_index);
printf("Worker %d: ready\n", pwork->thread_index);
SetEvent(pwork->event_done);
}
}
int main()
{
for (int i = 0; i < NUM_THREADS; ++i)
{
gg_work[i].thread_index = i;
gg_work[i].event_do = CreateEvent(NULL, FALSE, FALSE, TEXT("event_do"));
gg_work[i].event_done = CreateEvent(NULL, FALSE, FALSE, TEXT("event_done"));
gg_threads[i] = _beginthread(my_iterate, 0, &gg_work[i]);
}
while (1)
{
for (int thread = 0; thread < NUM_THREADS; ++thread)
{
printf("Master: send request to %d\n", thread);
SetEvent(gg_work[thread].event_do);
}
for (int thread = 0; thread < NUM_THREADS; ++thread)
{
printf("Master: wait for worker %d\n", thread);
WaitForMultipleObjects(1, &gg_work[thread].event_done, TRUE, INFINITE);
printf("Master: got results from worker %d\n", thread);
}
}
}
I expect it to run endlessly, but it always gets stuck after sending a few events back and forth.我希望它可以无休止地运行,但是在来回发送一些事件后它总是卡住。 Here is an example output:
这是一个示例输出:
Master: send request to 0
Master: send request to 1
Master: wait for worker 0
Worker 1: wait for request
Worker 0: wait for request
Worker 1: working
Worker 1: ready
Worker 1: wait for request
Master: got results from worker 0
Master: wait for worker 1
Here, the master sets event for worker 0;这里,master为worker 0设置事件; worker 0 waits for it but never wakes up.
工人 0 等待它但永远不会醒来。
It doesn't always get stuck immediately - sometimes it manages to send a few requests back and forth.它并不总是立即卡住 - 有时它会设法来回发送一些请求。 But if I use only one worker thread, it runs infinitely (ie success).
但是如果我只使用一个工作线程,它会无限运行(即成功)。
What am I doing wrong?我究竟做错了什么? Should I implement communication between threads using different WinAPI functions?
我应该使用不同的 WinAPI 函数实现线程之间的通信吗? If yes, why?
如果是,为什么?
If I replace SetEvent
/ WaitFor...
by busy-waiting on a boolean flag, protected by a mutex, it works, but is very slow.如果我通过忙等待一个受互斥锁保护的布尔标志来替换
SetEvent
/ WaitFor...
,它可以工作,但速度很慢。
I use Visual Studio 2017 to compile my code;我使用 Visual Studio 2017 来编译我的代码; I link it with the "Multi-threaded Debug DLL" runtime library.
我将它与“多线程调试 DLL”运行时库链接起来。
i can not reproduce deadlock even with original (by sense) code, need debug concrete binary for understand.即使使用原始(按感觉)代码,我也无法重现死锁,需要调试具体的二进制文件才能理解。
struct mywork
{
HANDLE event_do = 0, event_done = 0;
ULONG i, n;
~mywork()
{
if (event_do) CloseHandle(event_do);
if (event_done) CloseHandle(event_done);
}
};
ULONG WINAPI WorkThread(PVOID param)
{
ULONG i = reinterpret_cast<mywork*>(param)->i,
n = reinterpret_cast<mywork*>(param)->n;
HANDLE event_do = reinterpret_cast<mywork*>(param)->event_do,
event_done = reinterpret_cast<mywork*>(param)->event_done;
PSTR prefix = (PSTR)alloca(i+1);
memset(prefix, '\t', i);
prefix[i] = 0;
do
{
printf("%sWorker[%u]: wait (%u)\n", prefix, i, n);
WaitForSingleObject(event_do, INFINITE);
printf("%sWorker[%u]: .... (%u)\n", prefix, i, n);
printf("%sWorker[%u]: done (%u)\n", prefix, i, n);
SetEvent(event_done);
} while (--n);
printf("%sWorker[%u]: exit\n", prefix, i);
return 0;
}
void testm()
{
mywork my[NUM_THREADS], *pmy = my;
ULONG i = _countof(my), nThreads = 0, n = task_count;
do
{
pmy->i = i;
pmy->n = n;
if ((pmy->event_do = CreateEvent(0, 0, 0, 0)) &&
(pmy->event_done = CreateEvent(0, 0, 0, 0)))
{
if (HANDLE hThread = CreateThread(0, 0, WorkThread, pmy++, 0, 0))
{
CloseHandle(hThread);
nThreads++;
}
}
} while (--i);
if (nThreads)
{
do
{
pmy = my, i = nThreads;
do
{
printf("Master: signal [%u] (%u)\n", pmy->i, n);
SetEvent(pmy++->event_do);
} while (--i);
pmy = my, i = nThreads;
do
{
printf("Master: wait for [%u] (%u)\n", pmy->i, n);
WaitForSingleObject(pmy->event_done, INFINITE);
printf("Master: result from [%u] (%u)\n", pmy->i, n);
} while (pmy++, --i);
} while (--n);
printf("Master: exit\n");
}
}
but anyway this code containing serious logic error - wait separate in loop for every thread.但无论如何,这段代码包含严重的逻辑错误 - 为每个线程在循环中单独等待。 this make dependency between threads (say one thread fast complete self job, but we wait for another thread long time. as result "fast" thread also wait for "slow" thread)
这使线程之间产生依赖性(比如一个线程快速完成自己的工作,但我们等待另一个线程很长时间。结果“快速”线程也等待“慢”线程)
correct solution - wait on all events at once (but must be not more that 64 threads in this case).正确的解决方案 - 一次等待所有事件(但在这种情况下不能超过 64 个线程)。 so code can be next:
所以代码可以是下一个:
struct mywork
{
HANDLE event_do = 0, event_done = 0;
ULONG i, n;
~mywork()
{
if (event_do) CloseHandle(event_do);
if (event_done) CloseHandle(event_done);
}
};
ULONG WINAPI WorkThread(PVOID param)
{
ULONG i = reinterpret_cast<mywork*>(param)->i,
n = reinterpret_cast<mywork*>(param)->n;
HANDLE event_do = reinterpret_cast<mywork*>(param)->event_do,
event_done = reinterpret_cast<mywork*>(param)->event_done;
PSTR prefix = (PSTR)alloca(i+1);
memset(prefix, '\t', i);
prefix[i] = 0;
for(WaitForSingleObject(event_do, INFINITE);;)
{
printf("%sWorker[%u]: .... (%u)\n", prefix, i, n);
printf("%sWorker[%u]: done (%u)\n", prefix, i, n);
if (--n)
{
printf("%sWorker[%u]: wait (%u)\n", prefix, i, n);
SignalObjectAndWait(event_done, event_do, INFINITE, FALSE);
}
else
{
SetEvent(event_done);
break;
}
}
printf("%sWorker[%u]: exit\n", prefix, i);
return 0;
}
void testm()
{
mywork my[NUM_THREADS], *pmy = my;
ULONG i = _countof(my), nThreads = 0, n = 8;
do
{
pmy->i = i;
pmy->n = n;
if ((pmy->event_do = CreateEvent(0, 0, 0, 0)) &&
(pmy->event_done = CreateEvent(0, 0, 0, 0)))
{
if (HANDLE hThread = CreateThread(0, 0, WorkThread, pmy++, 0, 0))
{
CloseHandle(hThread);
nThreads++;
}
}
} while (--i);
if (nThreads)
{
HANDLE Events[_countof(my)], *pEvent = Events;
pmy = my, i = nThreads;
do
{
*pEvent++ = pmy->event_done;
printf("Master: signal [%u] (%u)\n", pmy->i, n);
SetEvent(pmy++->event_do);
} while (--i);
n = nThreads;
do
{
i = WaitForMultipleObjects(nThreads, Events, FALSE, INFINITE);
if (i < nThreads)
{
pmy = my + i;
printf("Master: result from [%u] (%u)\n", i, pmy->n);
if (--pmy->n)
{
printf("Master: signal [%u] (%u)\n", pmy->i, pmy->n);
SetEvent(pmy->event_do);
}
else
{
--n;
}
}
else
{
__debugbreak();
}
} while (n);
printf("Master: exit\n");
}
}
but anyway this not good enough solution.但无论如何这还不够好解决方案。 not need create
event_done
events.不需要创建
event_done
事件。 better if every thread have for example HWHD
handle of main thread which run message loop and SendMessage
to it window.如果每个线程都有例如运行消息循环和
SendMessage
到它的窗口的主线程的HWHD
句柄, HWHD
更好了。 in this case even event_do
not need, because SendMessage
wait internal.在这种情况下甚至
event_do
都不需要,因为SendMessage
内部等待。 possible and post APC to main thread too.可能并将 APC 也发布到主线程。
code with APC:带有APC的代码:
struct mywork
{
HANDLE event_do = 0, hMainThread;
ULONG i, n, *pnThreads;
~mywork()
{
if (event_do) CloseHandle(event_do);
}
};
VOID NTAPI EventDone(_In_ mywork* pmy)
{
printf("Master: result from [%u] (%u)\n", pmy->i, pmy->n);
if (--pmy->n)
{
printf("Master: signal [%u] (%u)\n", pmy->i, pmy->n);
SetEvent(pmy->event_do);
}
else
{
--*pmy->pnThreads;
}
}
ULONG WINAPI WorkThread(PVOID param)
{
ULONG i = reinterpret_cast<mywork*>(param)->i,
n = reinterpret_cast<mywork*>(param)->n;
HANDLE event_do = reinterpret_cast<mywork*>(param)->event_do,
hMainThread = reinterpret_cast<mywork*>(param)->hMainThread;
PSTR prefix = (PSTR)alloca(i+1);
memset(prefix, '\t', i);
prefix[i] = 0;
do
{
printf("%sWorker[%u]: wait (%u)\n", prefix, i, n);
WaitForSingleObject(event_do, INFINITE);
printf("%sWorker[%u]: .... (%u)\n", prefix, i, n);
printf("%sWorker[%u]: done (%u)\n", prefix, i, n);
QueueUserAPC((PAPCFUNC)EventDone, hMainThread, (ULONG_PTR)param);
} while (--n);
printf("%sWorker[%u]: exit\n", prefix, i);
return 0;
}
void testm()
{
mywork my[NUM_THREADS], *pmy = my;
ULONG i = _countof(my), nThreads = 0, n = task_count;
if (HANDLE hMainThread = OpenThread(THREAD_SET_CONTEXT, FALSE, GetCurrentThreadId()))
{
do
{
pmy->i = i;
pmy->n = n;
pmy->hMainThread = hMainThread;
pmy->pnThreads = &nThreads;
if (pmy->event_do = CreateEvent(0, 0, 0, 0))
{
if (HANDLE hThread = CreateThread(0, 0, WorkThread, pmy++, 0, 0))
{
CloseHandle(hThread);
nThreads++;
}
}
} while (--i);
if (nThreads)
{
pmy = my, i = nThreads;
do
{
printf("Master: signal [%u] (%u)\n", pmy->i, n);
SetEvent(pmy++->event_do);
} while (--i);
while (STATUS_USER_APC == SleepEx(INFINITE, TRUE) && nThreads) continue;
}
CloseHandle(hMainThread);
}
printf("Master: exit\n");
}
code with windows messages:带有 Windows 消息的代码:
struct mywork
{
HWND hwnd;
ULONG i, n;
enum { WM_DONE = WM_USER };
};
ULONG WINAPI WorkThread(PVOID param)
{
ULONG i = reinterpret_cast<mywork*>(param)->i,
n = reinterpret_cast<mywork*>(param)->n;
HWND hwnd = reinterpret_cast<mywork*>(param)->hwnd;
PSTR prefix = (PSTR)alloca(i+1);
memset(prefix, '\t', i);
prefix[i] = 0;
do
{
printf("%sWorker[%u]: .... (%u)\n", prefix, i, n);
printf("%sWorker[%u]: done and wait (%u)\n", prefix, i, n);
SendMessageW(hwnd, mywork::WM_DONE, 0, (LPARAM)param);
} while (--n);
printf("%sWorker[%u]: exit\n", prefix, i);
return 0;
}
LRESULT WINAPI WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_NCDESTROY:
PostQuitMessage(0);
break;
case WM_NCCREATE:
SetWindowLongPtrW(hwnd, GWLP_USERDATA,
(LONG_PTR)reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams);
break;
case mywork::WM_DONE:
mywork* pmy = (mywork*)lParam;
printf("Master: result from [%u] (%u)\n", pmy->i, pmy->n);
if (--pmy->n)
{
printf("Master: signal [%u] (%u)\n", pmy->i, pmy->n);
}
else
{
PULONG pnThreads = (PULONG)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
if (!--*pnThreads)
{
DestroyWindow(hwnd);
}
break;
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
void testm()
{
mywork my[NUM_THREADS], *pmy = my;
ULONG i = _countof(my), nThreads = 0, n = task_count;
WNDCLASS cls = { 0, WndProc, 0, 0, 0, 0, 0, 0, 0, L"my" };
if (RegisterClassW(&cls))
{
if (HWND hwnd = CreateWindowExW(0, cls.lpszClassName,
0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, 0, &nThreads))
{
do
{
pmy->i = i;
pmy->n = n;
pmy->hwnd = hwnd;
if (HANDLE hThread = CreateThread(0, 0, WorkThread, pmy++, 0, 0))
{
CloseHandle(hThread);
nThreads++;
}
} while (--i);
if (nThreads)
{
MSG msg;
while (0 < GetMessageW(&msg, 0, 0, 0))
{
DispatchMessageW(&msg);
}
}
}
UnregisterClassW(cls.lpszClassName, 0);
}
printf("Master: exit\n");
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.