CreateEvent
定义
CreateEvent是Windows API中的一个同步函数,作用为创建或打开一个命名或未命名的事件对象,并返回该对象的句柄。其函数原型声明为HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPTSTR lpName),共4个参数[2][1]。在头文件中,CreateEvent是一个根据UNICODE宏选择编码的通用名称,实际展开为ANSI版本CreateEventA或Unicode版本CreateEventW,两者分别对应窄字符与宽字符的名称参数[6][3]。
该函数的最低客户端要求为Windows XP,最低服务器要求为Windows Server 2003,头文件为synchapi.h(亦可经Windows.h引入),链接库为Kernel32.lib,动态链接库为Kernel32.dll[6]。若需要自行指定访问掩码,微软建议改用功能更完整的CreateEventEx函数[1][7]。
逐参数来看:lpEventAttributes指向SECURITY_ATTRIBUTES结构,用于决定句柄能否被子进程继承,并可为新事件提供安全描述符;传入NULL时句柄不可继承,事件获得默认安全描述符,其访问控制列表来自创建者的令牌[1]。bManualReset指定事件对象是手动重置型还是自动重置型;bInitialState指定创建时是否为已信号状态;lpName可给出对象名称,名称长度受MAX_PATH限制且区分大小写,传入NULL则创建无名对象[2][3]。函数成功时返回事件对象句柄,失败时返回NULL,具体错误须通过GetLastError获取[2]。
原理
事件对象是一种内核同步对象,其状态只有已信号与未信号两种取值,可由SetEvent显式置为已信号[4]。线程调用等待函数后,只要指定对象仍处于未信号状态就会被阻塞;对象变为已信号时,等待随之结束,线程被释放继续执行[2]。因此事件对象适合把“某件事已经发生”这一信息从一个线程传递给另一个线程[4]。
两种重置方式的行为差别是理解该函数的关键。手动重置事件在被置为已信号后会一直保持该状态,直到有线程调用ResetEvent将其改回未信号,在此期间任意数量的等待线程,以及随后才开始等待的线程,都可以被放行[4]。自动重置事件只释放一个等待线程,系统随即自动把状态改回未信号;若当时没有线程在等待,则状态保持已信号,直到有人来取[2]。自动重置事件释放多个等待者时具体唤醒哪一个并不保证先进先出,内核模式APC等外部因素都可能改变等待次序[4]。用布尔条件描述手动重置事件的等待释放可写作:等待线程数 $n \ge 0$ 时,只要状态为已信号,则 $n$ 个线程全部被放行,直到某线程执行 ResetEvent。
对象命名是跨进程共享的基础,语义上可理解为一张按名称索引的对象表:当一个进程在CreateEvent中给出名称时,该名称与事件对象被登记到对应的命名空间;另一进程以同一名称调用CreateEvent或OpenEvent,即定位到同一对象而非新建[2]。名称前可加“Global\”或“Local\”前缀,显式指定在全局命名空间或会话命名空间中创建,其余字符除反斜杠外均可使用[1]。若名称与同一命名空间中其他类型对象(信号量、互斥体、可等待计时器、作业或文件映射对象)重名,函数会失败并返回ERROR_INVALID_HANDLE,因为这些对象共享同一命名空间[1]。
当lpName与已存在的命名事件重名时,函数并不新建对象,而是请求EVENT_ALL_ACCESS权限并返回既有对象的句柄,此时bManualReset与bInitialState因为已由创建进程设定而被忽略;调用者可通过GetLastError是否为ERROR_ALREADY_EXISTS判断自己究竟是创建者还是打开者[3][2]。新创建对象所返回的句柄具有EVENT_ALL_ACCESS访问权限,可直接用于任何接受事件句柄的函数;句柄用毕应以CloseHandle关闭,进程结束时系统会自动关闭,最后一个句柄关闭后对象即被销毁[2]。
发展历程
事件对象作为Win32同步机制的一部分,早在Windows NT系列与Windows 95/98时代即已存在,并随Windows CE 1.0及后续的Windows Mobile版本进入嵌入式平台;在这些平台上,该函数的头文件为winbase.h,链接库为coredll.lib与Nk.lib[2]。早期文档显示,Windows NT 4.0及更早版本中对象名称除反斜杠外可用任意字符,而“Global\”“Local\”前缀与终端服务会话命名空间的规则是在后续版本中逐步明确的[5]。
随着Windows API文档体系的整理,该函数被归入synchapi.h头文件,ANSI与Unicode两个版本分别记为CreateEventA与CreateEventW,最低支持范围放宽到Windows XP与Windows Server 2003[6]。由于原函数无法指定访问掩码,微软随后提供CreateEventEx,把安全属性与名称之外的复位方式、初始状态合并为标志位参数,并增加dwDesiredAccess参数,以满足更细粒度的权限控制需求[7][8]。在部分示例代码中,开发者仍偏好使用参数更简单的CreateEvent,而非访问控制更复杂的CreateEventEx[9]。
应用
最常见的用途是线程同步与线程退出通知。工作线程在循环中反复调用WaitForSingleObject检查事件状态,未收到信号就继续或保持阻塞;主线程或其他线程在条件满足时调用SetEvent,等待中的线程随即被唤醒[5]。若线程正阻塞在套接字收数据等操作上,通常需要先解除阻塞再发出事件,才能顺利退出[5]。
自动重置事件常被用作“一次性放行”的开关,例如多个线程争用同一资源时,每次信号只放行一个线程,该线程处理完毕后再次调用SetEvent把机会交给下一个等待者[10]。手动重置事件则适合广播式通知,一旦置位,所有等待者以及在置位期间开始等待的线程都可以继续运行,直到有线程显式复位[4]。
在重叠I/O中,系统会在操作完成时把指定的事件对象置为已信号,因此一个线程可以在多个并发操作中各自绑定不同的事件对象,再用多对象等待函数等待其中任意一个完成[4]。此外,由于事件对象可以命名并跨进程打开,它也被用于进程间同步,例如让一个进程在完成初始化后置位某个命名事件,另一进程以此判断何时可以开始工作[2]。
围绕该函数还形成了一组配套调用:OpenEvent按名称取得已有事件句柄,SetEvent与PulseEvent用于置位,ResetEvent用于复位,WaitForSingleObject与WaitForMultipleObjects用于等待,CloseHandle用于释放句柄[11]。其中PulseEvent的效果相当于先置位再立即复位,由于调用时无法确知是否有线程正在等待,其实际可用性有限,实践中较少采用[10]。
局限
CreateEvent本身只提供“置位/未置位”的二值状态,不记录唤醒次数,也不具备所有权概念:事件对象不属于任何线程,谁都可以调用SetEvent,同一线程再次等待也不会引发类似递归加锁的效果[12]。因此它适合做通知机制,若要实现互斥式的资源独占,需要另用互斥体或信号量[12]。
命名事件与其他内核对象共用同一命名空间,一旦名称冲突,CreateEvent会直接失败,而不区分对象类型[1]。同时,该函数无法在创建时指定访问掩码,也无法借参数细化安全设置,需要这些能力时必须改用CreateEventEx[1]。
基于内核对象的同步自身带有开销,与工作在用户态的临界区相比速度较慢,其价值在于能够跨进程使用[13]。此外,等待与置位之间的时序需要由调用者自行保证:自动重置事件若在无人等待时被置位,信号会被保留到有线程来取,但若置位发生在多个等待者之间,只有其中一个能继续运行,其余仍需等待下一次信号[4]。
参见
-
CreateEventEx —— 功能相同但可指定访问掩码与标志位的事件创建函数。
-
SetEvent —— 将事件对象置为已信号状态,从而放行等待线程。
-
ResetEvent —— 将事件对象复位为未信号状态。
-
WaitForSingleObject —— 等待单个内核对象变为已信号的函数。
-
互斥体 —— 具有所有权属性的同步对象,用于实现资源独占。
-
信号量 —— 通过计数控制并发数量的同步对象。
参考资料
- UID: NF:synchapi [引用日期2026-10-19]
- aa915030(v=msdn [引用日期2026-10-19]
- nf-synchapi-createeventw [引用日期2026-10-19]
- 事件物件 (同步處理) [引用日期2026-10-19]
- 线程中CreateEvent和SetEvent及WaitForSingleObject的用法 [引用日期2026-10-19]
- UID: NF:synchapi [引用日期2026-10-19]
- CreateEventExA 函数 (synchapi.h) - 跳至主要内容 跳到 Ask Learn 聊天体验 [引用日期2026-10-19]
- CreateEventEx [引用日期2026-10-19]
- github.com 上的网页 [引用日期2026-10-19]
- 多线程-事件 [引用日期2026-10-19]
- CreateEvent的用法 [引用日期2026-10-19]
- 问Win32事件对象是递归互斥对象吗?EN [引用日期2026-10-19]
- aliyun.com 上的网页 [引用日期2026-10-19]