CreateEvent 实现线程同步(Win32)
一、函数原型
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性,NULL默认
BOOL bManualReset, // TRUE=手动重置;FALSE=自动重置
BOOL bInitialState, // 初始状态:TRUE有信号,FALSE无信号
LPCTSTR lpName // 命名事件,NULL匿名(线程间一般匿名)
);
事件两种核心类型:
- 手动重置事件(bManualReset=TRUE):
SetEvent置为有信号后,一直保持有信号,必须手动调用ResetEvent才变回无信号。多个等待线程可以全部被唤醒。- 自动重置事件(bManualReset=FALSE):
SetEvent置有信号,只要唤醒1个等待线程,内核自动把事件变回无信号。适合一对一通知,防止多线程全部冲进去。
等待函数:
WaitForSingleObject(hEvent, INFINITE)
- INFINITE:无限等待,直到事件变为有信号状态才返回。
二、核心原理
事件内核对象有两种状态:有信号 / 无信号
- 线程调用
WaitForSingleObject阻塞,事件无信号时线程挂起; - 别的线程调用
SetEvent(hEvent)→ 事件变成有信号 → 等待线程被唤醒继续执行。
三、示例代码:自动重置事件(一对一通知)
场景:主线程创建子线程,子线程等待事件信号,主线程满足条件后发信号唤醒子线程
#include <windows.h>
#include <stdio.h>
// 事件句柄,全局方便多线程访问
HANDLE g_hEvent;
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
printf("子线程:开始等待事件信号...\n");
// 阻塞等待事件变为有信号
WaitForSingleObject(g_hEvent, INFINITE);
// 自动重置事件:唤醒后自动重置回无信号,不需要ResetEvent
printf("子线程:收到事件信号,开始执行任务\n");
return 0;
}
int main()
{
// 创建【自动重置事件】,初始状态:无信号
g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (g_hEvent == NULL)
{
printf("CreateEvent失败,err=%d\n", GetLastError());
return -1;
}
HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
CloseHandle(hThread);
Sleep(2000); // 模拟主线程做准备工作
printf("主线程:发送事件信号 SetEvent\n");
SetEvent(g_hEvent); // 唤醒等待的子线程
Sleep(1000);
CloseHandle(g_hEvent); // 释放内核对象
return 0;
}
四、手动重置事件示例(一次性唤醒多个线程)
#include <windows.h>
#include <stdio.h>
HANDLE g_hEvent;
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
int id = *(int*)lpParam;
printf("线程%d 等待事件...\n", id);
WaitForSingleObject(g_hEvent, INFINITE);
printf("线程%d 被唤醒!\n", id);
return 0;
}
int main()
{
// 手动重置事件,初始无信号
g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
HANDLE hThreads[3];
int ids[3] = {1,2,3};
for(int i=0;i<3;i++){
hThreads[i] = CreateThread(NULL,0,ThreadProc,&ids[i],0,NULL);
}
Sleep(2000);
printf("主线程:SetEvent,唤醒全部等待线程\n");
SetEvent(g_hEvent);
Sleep(1000);
ResetEvent(g_hEvent); // ⚠️手动重置必须手动关闭信号
for(int i=0;i<3;i++) CloseHandle(hThreads[i]);
CloseHandle(g_hEvent);
return 0;
}
五、常用API清单
SetEvent(hEvent):把事件设置为有信号ResetEvent(hEvent):把事件设置为无信号(手动事件才需要)WaitForSingleObject(h, dwMilliseconds):等待单个内核对象WaitForMultipleObjects():同时等待多个事件/内核对象CloseHandle(hEvent):必须释放句柄,否则内核资源泄漏
六、两种事件选型要点
| 类型 | bManualReset | 特点 | 使用场景 |
|---|---|---|---|
| 自动重置 | FALSE | SetEvent唤醒一个线程,自动变回无信号 | 生产者通知单个消费者,防止并发重入 |
| 手动重置 | TRUE | SetEvent唤醒所有等待线程,保持有信号 | 批量唤醒一组线程,需要手动ResetEvent关闭 |
七、常见坑
- 自动重置事件:如果
SetEvent的时候没有线程正在等待,信号会丢失。>例:主线程先SetEvent,子线程才开始Wait,自动事件的信号直接消失,子线程永久阻塞。手动重置事件不会丢信号,状态会保留。
- 忘记CloseHandle:内核对象不会释放,长期运行程序句柄泄漏。
- 多线程共享句柄:不要随意跨进程裸传句柄;跨进程要用命名事件或者句柄复制。
- 事件不能保护临界区(不能替代临界段/互斥量):事件是通知机制,不是互斥锁。如果要保护一段代码同一时间只能一个线程跑,要用
CRITICAL_SECTION或CreateMutex。