想象一下你正在组织一场线下技术沙龙,门口有两位志愿者负责发放限量版纪念品。如果两人同时去拿最后一份纪念品,很可能会出现重复发放的尴尬情况。这就是多线程编程中典型的资源竞争问题。在C++多线程开发中,事件(Event)对象就像是个智能对讲机,能精确协调不同线程的工作节奏。
我在处理一个票务系统项目时,就遇到过类似问题。当多个线程同时操作剩余票数变量时,如果不加控制,很可能出现超卖现象。Windows API提供的事件机制,通过信号状态管理,完美解决了这类同步需求。与互斥量(Mutex)和临界区(Critical Section)不同,事件对象更擅长处理"等待-通知"场景,比如生产者-消费者模型中,生产者线程完成任务后需要主动通知消费者线程。
创建事件就像配置一个智能开关,关键参数决定了它的工作方式:
cpp复制HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // 通常设为NULL
BOOL bManualReset, // 重置方式
BOOL bInitialState, // 初始状态
LPCTSTR lpName // 命名事件(可选)
);
我在实际项目中踩过的坑:bManualReset参数如果设置不当,会导致整个同步逻辑失效。手动重置(TRUE)就像会议室的手动灯开关,需要明确调用ResetEvent才能关闭信号;自动重置(FALSE)则像感应灯,被WaitForSingleObject检测到后会自动关闭。
cpp复制// 典型使用模式
SetEvent(g_hNewTicketArrived); // 通知消费者线程
WaitForSingleObject(g_hTicketSold, INFINITE); // 等待确认
让我们改进原始示例,增加更健壮的异常处理:
cpp复制// 全局变量
HANDLE g_hTicketEvent = NULL;
std::atomic<int> g_remainingTickets(100); // 使用原子操作
DWORD WINAPI SellerThread(LPVOID lpParam) {
while (true) {
WaitForSingleObject(g_hTicketEvent, INFINITE);
if (g_remainingTickets <= 0) {
SetEvent(g_hTicketEvent); // 通知其他线程退出
break;
}
// 模拟出票过程
std::cout << "售出票号:" << g_remainingTickets-- << std::endl;
// 关键:处理完立即重置事件
ResetEvent(g_hTicketEvent);
}
return 0;
}
在电商秒杀系统中,我做过对比测试:
cpp复制// 自动重置示例(单消费者)
g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
// 手动重置示例(多消费者)
g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
cpp复制class EventGuard {
public:
EventGuard(HANDLE hEvent) : m_hEvent(hEvent) {}
~EventGuard() { SetEvent(m_hEvent); }
private:
HANDLE m_hEvent;
};
cpp复制DWORD dwRet = WaitForSingleObject(hEvent, 5000);
if (dwRet == WAIT_TIMEOUT) {
// 触发超时处理流程
}
对于高并发场景,可以预创建事件对象池:
cpp复制std::vector<HANDLE> CreateEventPool(size_t count) {
std::vector<HANDLE> pool;
for (size_t i = 0; i < count; ++i) {
pool.push_back(CreateEvent(NULL, FALSE, FALSE, NULL));
}
return pool;
}
在最近的一个分布式任务调度系统中,使用事件池使得线程同步开销降低了40%。记住一定要在程序退出时正确关闭所有事件句柄,否则会导致资源泄漏。