c++多线程(6)------ 条件变量
- 操作系统:ubuntu22.04
- IDE:Visual Studio Code
- 编程语言:C++11
条件变量(Condition Variable) 是实现线程间高效等待与通知机制的核心工具,通常与互斥锁配合使用,用于解决“生产者-消费者”、“任务队列”、“线程协调”等经典并发问题。
一、为什么需要条件变量?
❌ 问题:忙等待(Busy Waiting)效率低
std::mutex mtx;
bool ready = false;// 线程A(等待者)
while (!ready)
{// 空循环,持续检查
}
// do work...// 线程B(通知者)
{std::lock_guard<std::mutex> lock(mtx);ready = true;
}
- CPU 资源浪费:等待线程不断轮询,占用 CPU。
- 响应延迟:无法立即响应状态变化。
✅ 解决方案:条件变量
- 等待线程挂起(阻塞),不消耗 CPU。
- 通知线程唤醒等待线程。
- 高效、节能、响应及时。
二、C++11 中的条件变量类型
C++11 在 <condition_variable> 中提供了两种条件变量:
类型 | 说明 |
---|---|
std::condition_variable | 仅支持 std::unique_lockstd::mutex,性能更高,最常用 |
std::condition_variable_any | 支持任意满足 BasicLockable 的锁(如 shared_mutex),但性能略低 |
✅ 推荐优先使用 std::condition_variable。
三、基本用法:wait() 与 notify_one() / notify_all()
核心 API:
void wait(std::unique_lock<std::mutex>& lock);
template<class Predicate>
void wait(std::unique_lock<std::mutex>& lock, Predicate pred);
void notify_one(); // 唤醒一个等待线程
void notify_all(); // 唤醒所有等待线程
🌰 示例:简单线程同步
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>std::mutex mtx;
std::condition_variable cv;
bool ready = false;void worker()
{std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, []{ return ready; }); // 等待 ready == truestd::cout << "Worker: working now!\n";
}void starter()
{std::this_thread::sleep_for(std::chrono::seconds(1));{std::lock_guard<std::mutex> lock(mtx);ready = true;}cv.notify_one(); // 通知一个等待线程
}int main()
{std::thread t1(worker);std::thread t2(starter);t1.join(); t2.join();
}
输出:
Worker: working now!
四、深入理解 wait() 的工作机制
cv.wait(lock, pred) 等价于:
while (!pred())
{cv.wait(lock); // 内部:1. unlock mutex; 2. 阻塞等待; 3. 被唤醒后重新 lock
}
关键点:
- 自动释放锁:调用 wait() 时,会自动释放传入的 unique_lock,允许其他线程获取锁并修改共享状态。
- 原子性:释放锁 + 进入等待 是原子操作,避免通知丢失(lost wake-up)。
- 虚假唤醒(Spurious Wakeup):即使没有调用 notify,线程也可能被唤醒(POSIX 允许)。因此必须使用谓词(predicate) 检查条件。
✅ 所以永远不要写 cv.wait(lock); 而不带谓词!
五、经典应用:生产者-消费者模型(任务队列)
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>std::queue< int > task_queue;
std::mutex mtx;
std::condition_variable cv;
bool done = false;// 生产者
void producer( int id )
{for ( int i = 0; i < 5; ++i ){{std::lock_guard< std::mutex > lock( mtx );task_queue.push( id * 10 + i );std::cout << "Producer " << id << " produced " << id * 10 + i << "\n";}cv.notify_one(); // 通知消费者std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );}
}// 消费者
void consumer( int id )
{while ( true ){std::unique_lock< std::mutex > lock( mtx );cv.wait( lock, [] { return !task_queue.empty() || done; } );if ( done && task_queue.empty() )break;int task = task_queue.front();task_queue.pop();lock.unlock(); // 提前释放锁,减少临界区std::cout << "Consumer " << id << " processed " << task << "\n";std::this_thread::sleep_for( std::chrono::milliseconds( 150 ) );}
}int main()
{std::thread p1( producer, 1 );std::thread p2( producer, 2 );std::thread c1( consumer, 1 );std::thread c2( consumer, 2 );p1.join();p2.join();done = true;cv.notify_all(); // 通知所有消费者退出c1.join();c2.join();
}
输出:
Producer 1 produced 10
Consumer 2 processed 10Producer
2 produced 20
Consumer 1 processed 20
Producer 1 produced 11
Producer 2 produced 21
Consumer 2 processed 11
Consumer 1 processed 21
Producer 1 produced 12
Producer 2 produced 22
Consumer 2 processed 12
Consumer 1 processed 22
Producer 1 produced 13
Producer 2 produced 23
Producer 1 produced 14
Producer 2 produced 24
Consumer 2 processed 13
Consumer 1 processed 23
Consumer 1 processed 14
Consumer 2 processed 24
关键设计:
- 使用 done 标志优雅退出。
- cv.notify_one():一个任务唤醒一个消费者(避免惊群)。
- 消费后提前解锁,提高并发性。
六、带超时的等待:wait_for() 与 wait_until()
适用于“最多等待 N 秒”的场景。
std::unique_lock<std::mutex> lock(mtx);
if (cv.wait_for(lock, std::chrono::seconds(5), []{ return ready; }))
{std::cout << "Condition met!\n";
}
else
{std::cout << "Timeout! Condition not met.\n";
}
- wait_for:相对时间(duration)
- wait_until:绝对时间(time_point)
✅ 返回值:若因条件满足而唤醒,返回 true;若超时,返回 false。
七、常见错误与最佳实践
❌ 错误1:忘记使用谓词(导致虚假唤醒崩溃)
// 危险!可能虚假唤醒后继续执行
cv.wait(lock);
// 此时 ready 可能仍为 false!
✅ 正确:
cv.wait(lock, []{ return ready; });
❌ 错误2:在未加锁时修改条件并通知
ready = true; // ❌ 未加锁!
cv.notify_one(); // 可能通知丢失
✅ 正确:
{std::lock_guard<std::mutex> lock(mtx);ready = true;
}
cv.notify_one(); // 通知可在锁外,但修改必须在锁内
📝 通知可以在锁外调用(C++ 允许),但修改共享状态必须在锁内。
❌ 错误3:使用 std::lock_guard 与 wait()
std::lock_guard<std::mutex> lock(mtx);
cv.wait(lock, ...); // ❌ 编译错误!wait 需要 unique_lock
✅ 正确:
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, ...);
❌ 错误4:滥用 notify_all()
- 若只有一个线程能处理任务,用 notify_one() 更高效。
- notify_all() 适用于广播场景(如所有线程需响应“退出”信号)。
八、notify_one() vs notify_all()
场景 | 推荐 |
---|---|
任务队列(一个任务 → 一个消费者) | notify_one() |
状态变更需所有线程响应(如 shutdown) | notify_all() |
不确定有多少线程在等 | notify_all()(安全但低效) |
九、总结:条件变量使用模板
// 共享状态
bool condition = false;
std::mutex mtx;
std::condition_variable cv;// 等待线程
{std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, []{ return condition; });// 条件满足,处理逻辑
}// 通知线程
{std::lock_guard<std::mutex> lock(mtx);condition = true;
}
cv.notify_one(); // 或 notify_all()
最佳实践清单:
- 总是使用谓词版本的 wait()。
- 修改共享状态必须在互斥锁保护下。
- 使用 std::unique_lock(不是 lock_guard)。
- 优先用 notify_one(),除非需要广播。
- 考虑超时机制(wait_for)避免永久阻塞。
- 避免在持有锁时做耗时操作(提前 unlock)。