一句话先分清:同步/异步是两套维度,不要混在一起

很多人会搞混两个概念:

  1. 线程同步(Thread Synchronization):是【多个线程之间协调顺序、互相通知、防止乱序竞争】的术语,这里的“同步”≠同步IO那种阻塞调用
  2. 异步(Asynchronous):一般指【发起任务之后不阻塞当前线程,继续往下跑】。

CreateEvent 属于线程同步原语,它的作用就是做线程之间的同步协调,但等待事件这个动作本身,可以是阻塞(同步等待),也可以配合IO完成端口做成异步等待。


1. 为什么叫“线程同步”?

“同步”在这里的本意:让多个线程步调保持一致。 举个例子:

  • 线程A干活,线程B必须等A干完才能继续;
  • A干完调用 SetEvent 发通知;
  • B调用 WaitForSingleObject 卡住,直到收到信号才继续。

👉 两个线程的执行节奏被协调统一了,所以叫线程同步

  • WaitForSingleObject(hEvent, INFINITE)阻塞式等待(同步等待)——线程停在这里不动,直到事件有信号。
  • 这个阻塞等待,是同步等待API;但业务逻辑本身是:A和B两个线程异步并发运行,靠事件来同步通知。

✅ 通俗一句话:线程是异步并发跑的,但是我们用事件做同步,控制它们的执行时序。 线程并发(异步执行) ≠ 不能做同步控制。

2. 对比:什么叫异步等待事件?

WaitForSingleObject 是同步阻塞等待。 Windows 还支持把事件绑定到 IOCP,用 GetQueuedCompletionStatus,或者 RegisterWaitForSingleObject

  • 注册等待事件;
  • 主线程不阻塞,继续做别的事情;
  • 事件变成有信号时,系统自动回调一个函数。 这就叫异步等待事件

所以:事件对象本身只是一个信号标记,它不决定是同步等待还是异步等待,取决于你用哪种方式等待它。

3. 疑问:“线程不都是异步跑的吗?为啥还要同步?”

没错,Windows 的多个线程本身就是并发、异步独立执行,CPU交替调度。 但异步并发会带来问题:

  1. 任务依赖:子线程任务必须等主线准备好数据才能开始;
  2. 竞态:多个线程同时读写同一块内存,数据错乱;
  3. 任务通知:生产者生产完数据,通知消费者开始处理。

线程天生异步乱跑,同步原语(事件、互斥量、信号量、临界区)就是用来约束、协调这些乱跑线程的执行顺序

举个现实类比:

  • 两个工人(线程)各自干活,属于异步并行;
  • 工人B必须等工人A把零件送过来才能组装;
  • 零件送到按一下铃铛(SetEvent);B一直在等铃铛响(Wait)。 工人两个人是并行异步干活,但靠铃铛做同步协调。 👉 工人异步执行,铃铛(事件)实现同步。

4. 重点区分容易混淆名词

表格

名词 含义
线程同步(Thread Sync) 协调多线程执行顺序,解决依赖 / 竞争(事件、mutex 都属于同步对象)
同步等待 调用 API,线程阻塞卡住,直到条件满足(WaitForSingleObject)
异步等待 发起等待,线程不阻塞;条件满足自动回调(RegisterWaitForSingleObject)
异步并发线程 多个线程独立调度,互不等待,同时运行

5. 结合你游戏脚本场景举例

比如你多开游戏账号的线程:

  • 几十个账号线程各自循环跑(异步并发);
  • 但是你想:全部账号先暂停,等主控制线程下发 “开始打怪” 指令,所有线程才一起启动。 这时候就用手动重置 Event
  1. 所有子线程都 WaitForSingleObject(hStartEvent, INFINITE),全部阻塞;
  2. 主线程准备完成,调用 SetEvent(hStartEvent)
  3. 所有子线程同时唤醒,开始执行。

👉 子线程平时独立异步跑;启动这个动作,靠事件做同步控制。

6. 额外提醒:Event 擅长【通知】,不擅长【互斥保护共享变量】

  • Event:信号通知(我做完了,通知你)
  • Mutex / CriticalSection:互斥(同一时间只允许一个线程进入代码段,保护共享数据) 很多新手混淆:Event 只能通知,不能替代锁来保护共享内存。