ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

条件变量与信号量:原理及实战解析

条件变量与信号量:原理及实战解析 上篇聊互斥锁的时候有位读者留言说锁管住了临界区但管不住“等待”。我当时觉得这句话很精辟——实际项目里你碰到的大多数并发问题不是“两个人抢一把椅子”而是“一个人等一锅饭”。线程池里的任务队列、流水线上的缓冲区、网络服务的连接池本质都是在等某个条件成立队列有数据了、缓冲区腾出位置了、资源被释放了。用互斥锁处理这种“等条件”的场景唯一的办法就是轮询加休眠我之前 review 同事线程池代码时看到的 while(1) usleep(1000) 就是这么干的。线程一多CPU 空转和延迟放大得很明显。这篇文章就把解决这类问题的两个核心原语讲透条件变量的深度原理以及 POSIX 信号量的实战用法最后配一份可直接跑的生产者-消费者代码。1. 先治“忙等病”条件变量的设计动机与基本规则1.1 轮询等待能跑但代价很高先看一段大多数人都写过的轮询代码for (;;) { pthread_mutex_lock(mtx); int ready check_condition(); pthread_mutex_unlock(mtx); if (ready) break; usleep(1000); // 睡 1ms }这段代码逻辑没错但它同时犯了三个错。第一个是延迟不可控。事件发生在两次轮询之间的任意时刻最坏情况下要等整整一个 sleep 周期才能被处理平均延迟被硬生生拉到了 500 微秒以上。对高频交易、实时音视频这种对延迟敏感的场景这是不可接受的。第二个是 CPU 空转。usleep 虽然让出 CPU但它仍然会按内核调度周期唤醒线程每次唤醒都要执行一次条件检查和锁操作。线程少的时候看不出来线程池里挂三五十个线程每分钟无谓唤醒上万次CPU 占用和功耗立刻暴露。第三个是公平性。轮询线程的唤醒顺序由内核调度决定谁运气好谁先醒晚醒的线程可能发现条件已经被抢走又要回去继续睡。在高竞争场景下某些线程长时间抢不到资源是家常便饭。我并不是说轮询完全不能用某些超短等待场景下它反而比条件变量简单直接。但作为一个通用同步原语轮询在延迟、功耗、扩展性三方面都垫底。1.2 条件变量不是“条件”本身而是一套通知机制条件变量condition variable这个名字很有迷惑性初学者容易把它当成“条件”本身——好像条件变量里存了一个布尔值wait 的人读一下就知道了。这是最大的误解。条件变量的设计哲学是共享变量负责记录状态条件变量只负责通知状态变化。我习惯用一个类比去理解共享变量是一块黑板上面写着“缓冲区还剩几个空位”条件变量是一枚铃铛生产者写完黑板之后摇铃等在旁边的消费者被铃声叫醒再到黑板前确认具体的数字。这里的关键在于铃铛不记录任何状态。它不会告诉你“还剩 3 个空位”它只负责叫醒你。醒过来之后你必须自己去看黑板判断条件是否真的满足。这就是后面反复强调“while 循环判断”的根本原因——等的人醒来不等于条件成立可能条件又被别人改了可能是有人误摇铃甚至可能没有人摇铃系统自己醒了。条件变量的 API 只有三件事pthread_cond_wait(cond, mtx)把当前线程挂到 cond 的等待队列上同时释放 mt
返回列表