一、std::condition_variable
condition_variable是我们C++封装后的互斥量
condition_variable需要配合互斥锁系列进行使用,主要提供wait和notify系统接口。
wait需要传递⼀个unique_lock类型的互斥锁,wait会阻塞当前线程直到被notify。在进入阻塞的⼀瞬间,会解开互斥锁,方便其他线程获取锁,访问条件变量。当被notify唤醒时,他会 同时获取到锁,再继续往下运行。
notify_one会唤醒当前条件变量上等待的其中⼀个线程,使用时他也需要用互斥锁保护,如果没有现成阻塞等待,他啥事都不做;notify_all会唤醒当前条件变量上等待的所有线程线程。 condition_variable_any 类是std::condition_variable 的泛化。
相对于只在 std::unique_lock 上工作的std::condition_variable,condition_variable_any 能在 任何满足可基本锁定(BasicLockable)要求的锁上工作。
这里重点介绍一下condition_variable的wait()接口:
wait()接口有两种传参方式,例如下图

这里重点讲第二个传参方式的第二个参数
关于第二个参数文档是这样介绍的:
也就是说第二个参数我们可以传递一个返回bool类型的回调函数,该函数仅在
pred返回假 时阻塞线程;只有当pred变为真时,通知信号才能解除线程阻塞。也就是说假如我们在一个循环体中,为了防止里面循环一直执行,但是条件变量又没有被唤醒,导致一直占用资源,我们就可以用wait()接口的第二个参数来阻塞。
下面演示一个经典的问题,两个线程交替打印奇数和偶数
cpp
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
using namespace std;
int main()
{
std::mutex mtx;
std::condition_variable c;
int n = 100;
bool flag = true;
thread t1([&](){
int i=0;
while(i<n)
{
unique_lock<mutex> lock(mtx);
while(!flag)
{
c.wait(lock);
}
cout<<i<<endl;
flag = !flag;
i+=2;
c.notify_all();
}
});
thread t2([&](){
int j=1;
while(j<n)
{
unique_lock<mutex> lock(mtx);
while(flag)
{
c.wait(lock);
}
cout<<j<<endl;
flag = !flag;
j+=2;
c.notify_all();
}
});
t1.join();
t2.join();
}
二、std::future全家桶介绍
2.1 概览
为什么C++11会有future这套组件呢?
在 C++11 之前,想做异步任务、线程间传值、等待任务完成,只能用裸线程 + 互斥锁 + 条件变量,代码繁琐、容易死锁、传值麻烦。
C++11 推出了一套异步任务标准库:
- 不用手动管理线程、锁、条件变量
- 安全在线程间传递返回值 / 异常
- 优雅等待任务完成、查询任务状态
这套标准库的核心就是:
std::future异步结果获取器。
核心组件总览
bash
组件 作用
std::future 只读的异步结果,只能被一个线程获取
std::shared_future 可共享的异步结果,多线程都能获取
std::async 最简单:启动异步任务,直接返回 future
std::launch 控制 async 的启动策略(同步 / 异步 / 自动)
std::promise 手动设置值,把值塞给 future
std::packaged_task 包装函数 / 可调用对象,自动把返回值给 future
std::future_status 查询 future 任务状态(完成 / 超时 / 未就绪)
一句话总结:async /promise/packaged_task 是 "生产者",负责产生异步结果;future/shared_future 是 "消费者",负责获取结果。
2.2 基础:std::future(异步结果的唯一持有者)
future是一个模板类 ,用来获取异步任务的结果(返回值 / 异常)。核心特性
- 只能移动,不能拷贝(一个结果只能被一个 future 持有)
- 调用
.get()会阻塞直到结果就绪- 只能调用一次
.get(),第二次会抛异常核心方法
cpp// 阻塞等待,获取结果(只能调用一次) T get(); // 非阻塞查询任务状态 future_status wait_for(时间); future_status wait_until(时间点); // 纯阻塞等待,不获取结果 void wait(); // 判断是否有共享状态(是否有效) bool valid() const;
2.3 std::async
async是最推荐、最简单的异步任务启动方式,不用管线程,一行代码启动异步任务。
要配合future一起使用
sync有两种传参方式:
第一个传参方式是回调函数+形参
第二个传参方式是任务启动策略+回调函数+新参(任务启动策略下一小节讲,所以这里只演示第一种传参方式的使用方法)
样例:
cpp#include<iostream> #include<future> #include<chrono> using std::cout; using std::endl; int calc(int x) { std::this_thread::sleep_for(std::chrono::seconds(2)); return x * 2; } int main() { //1.启动异步任务,返回future std::future<int> f = std::async(calc, 10); cout << "任务已启动,等待结果..." << endl; //2.阻塞等待结果 int res = f.get(); cout << "结果:" << res << endl; // 输出 20 return 0; }
2.4 任务启动策略std::launch
利用launch我们可以放入async的第一个参数决定任务如何执行:
cpp// 枚举值 launch::async // 立即创建新线程,异步执行(强制异步) launch::deferred // 不创建线程,直到调用 get() 才在当前线程同步执行(懒加载) launch::async | launch::deferred // 默认值,系统自动选择区分launch::async与launch::deferred区别:
launch::async任务策略样例:
cpp#include<iostream> #include<future> #include<chrono> using std::cout; using std::endl; int calc(int x) { cout << "异步任务开始启动" << endl; std::this_thread::sleep_for(std::chrono::seconds(2)); return x * 2; } int main() { //1.启动异步任务,返回future std::future<int> f = std::async(std::launch::async,calc, 10); std::this_thread::sleep_for(std::chrono::seconds(1)); cout << "任务已启动,等待结果..." << endl; //2.阻塞等待结果 int res = f.get(); cout << "结果:" << res << endl; // 输出 20 return 0; }输出结果:
cpp异步任务开始启动 任务已启动,等待结果... 结果:20launch::deferred样例:
cpp#include<iostream> #include<future> #include<chrono> using std::cout; using std::endl; int calc(int x) { cout << "异步任务开始启动" << endl; std::this_thread::sleep_for(std::chrono::seconds(2)); return x * 2; } int main() { //1.启动异步任务,返回future std::future<int> f = std::async(std::launch::deferred,calc, 10); std::this_thread::sleep_for(std::chrono::seconds(3)); cout << "任务已启动,等待结果..." << endl; //2.阻塞等待结果 int res = f.get(); cout << "结果:" << res << endl; // 输出 20 return 0; }输出结果:
bash任务已启动,等待结果... 异步任务开始启动 结果:20
2.5 任务状态查询:std::future_status
future_status提供了我们可以非阻塞查询任务是否完成,避免异步任务长,调用线程一直阻塞,等待任务完成。
例如:
cppfuture<int> f = async(calc, 10); int res = f.get(); // 死等,calc 跑多久,这里就卡多久future_status是枚举,有 3 种状态:
1.ready:任务完成,结果就绪
2.timeout:等待超时,任务未完成
3.deferred:任务是 deferred 类型,未执行
样例:
cpp#include<iostream> #include<future> #include<chrono> using std::cout; using std::endl; int calc(int x) { cout << "异步任务开始启动" << endl; std::this_thread::sleep_for(std::chrono::seconds(3)); return x * 2; } int main() { //1.启动异步任务,返回future std::future<int> f = std::async(std::launch::async,calc, 10); auto status = f.wait_for(std::chrono::seconds(2)); // 最多等2秒 //2.任务状态 if (status == std::future_status::ready) { cout << "完成:" << f.get() << endl; } else if (status == std::future_status::timeout) { cout << "2秒超时,任务还在跑" << endl; // 可以选择继续等,或做别的事 } return 0; }注意:我们的wait_for返回值是std::future_status类型,作用:等待一段时间,超时就返回。
wait_until同理,只不过是绝对时间点
样例的输出结果:
cpp异步任务开始启动 2秒超时,任务还在跑
2.6 多线程共享结果:std::shared_future
future只能移动、只能 get() 一次,多线程都想获取同一个结果时必须用 shared_future。
相比于future不可以拷贝,shared_future是支持拷贝的,且多个线程可以同时get(),但是结果只计算一次,所有线程拿到同一份
我们可以通过future的share()接口得到其shared_future
样例:
cpp#include<iostream> #include<future> #include<chrono> #include<thread> #include<mutex> // 必须加 using std::cout; using std::endl; std::mutex mtx; // 全局互斥锁,保护cout int calc(int x) { cout << "异步任务开始启动" << endl; std::this_thread::sleep_for(std::chrono::seconds(3)); return x * 2; } int main() { std::future<int> f = std::async(calc, 10); std::shared_future<int> sf = f.share(); auto func = [](std::shared_future<int> sf) { std::lock_guard<std::mutex> lock(mtx); // 加锁,保证整行输出不被打断 cout << "线程: " << std::this_thread::get_id() << " 获取结果:" << sf.get() << endl; }; std::thread t1(func, sf); std::thread t2(func, sf); t1.join(); t2.join(); return 0; }适用场景:一个任务,多个消费者线程等待结果。
2.7 手动设置值:std::promise(底层生产者)
promise是手动向 future 塞值 的工具,适合线程间手动通信。
执行流程
- 创建
promise<T>- 从 promise 获取
future<T>- 线程 A:通过
promise.set_value(...)设置结果- 线程 B:通过
future.get()获取结果样例:
cppvoid thread_func(promise<int>& p) { this_thread::sleep_for(chrono::seconds(1)); p.set_value(99); // 手动设置结果 } int main() { promise<int> p; future<int> f = p.get_future(); // 绑定 future thread t(thread_func, ref(p)); // 启动线程 cout << "等待结果..." << endl; cout << f.get() << endl; // 99 t.join(); return 0; }
2.8 包装函数任务:std::packaged_task
packaged_task= 包装一个函数 / 仿函数 /lambda,自动把返回值 / 异常传给 future。它是
async的底层实现 之一,比async更灵活(可以自己控制线程,所以相比与async我们可以与线程池配合使用)。执行流程
- 包装函数 →
packaged_task<T(Args...)>- 获取
future- 扔到线程执行
- 获取结果
cppint calc(int a, int b) { return a + b; } int main() { // 包装函数 packaged_task<int(int, int)> task(calc); // 获取 future future<int> f = task.get_future(); // 手动创建线程执行任务 thread t(move(task), 10, 20); //上述代码就等价于async(F f,Args args...) cout << f.get() << endl; // 30 t.join(); return 0; }package_task只支持移动不支持拷贝(大概是因为future只支持一个线程拥有,所以future只支持移动,不支持拷贝,然后package_task又与一个future绑定,所以package_task也只支持移动,不支持拷贝)
2.9 容易犯错的坑
async 如果不接收返回值,会同步阻塞
async(launch::async, calc); // 会阻塞!必须用 future 接收:
auto f = async(...)future 无效时调用 get/wait 会崩溃 先用
f.valid()判断。异常会自动传递 异步任务抛异常,
get()时会在当前线程抛出。例如抛异常的场景样例:
cppint div(int a, int b) { if(b == 0) throw runtime_error("除数不能为0"); return a / b; } int main() { packaged_task<int(int,int)> task(div); future<int> f = task.get_future(); thread t(move(task), 10, 0); try { cout << f.get() << endl; } catch (exception& e) { cout << "捕获异常:" << e.what() << endl; } t.join(); return 0; }
2.10 经典场景C++11实现的线程池
那么看完我之前的C++11并发支持库(1)和(2)之后,我们可以写一个简易的C++11线程池
具体如下:
cpp#include <iostream> #include <vector> #include <queue> #include <thread> #include <mutex> #include <condition_variable> #include <functional> #include <future> #include <memory> using std::cout; using std::endl; class ThreadPool { public: explicit ThreadPool(size_t pool_count): pool_count_(pool_count),stop_(false) { for (size_t i = 0; i < pool_count_; ++i) { workers_.emplace_back([this]() { while (true) { std::function<void()> task; { std::unique_lock<std::mutex> lock(mtx_); // 等待任务或停止信号 cv_.wait(lock, [this]() { return stop_ || !tasks_.empty(); }); if (stop_ && tasks_.empty()) return; //线程池停止且无任务,退出 task = std::move(tasks_.front()); tasks_.pop(); } task(); } }); } } template<class F,class... Args> auto submit(F&& f, Args&&... args)->std::future<decltype(f(args...))> { using RetType = decltype(f(args...)); //用包装器再次封装函数,从而可以得到future类型,调用线程可以调用后,得到执行结果 auto task = std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...)); std::future<RetType> res = task->get_future(); { std::unique_lock<std::mutex> lock(mtx_); if (stop_) throw std::runtime_error("ThreadPool has stopped"); tasks_.emplace([task]() { (*task)(); }); } cv_.notify_one(); return res; } ~ThreadPool() { { std::unique_lock<std::mutex> lock(mtx_); stop_ = true; } cv_.notify_all(); for (auto& t : workers_) { t.join(); } } //进制拷贝和移动 ThreadPool(const ThreadPool&) = delete; ThreadPool& operator=(const ThreadPool&) = delete; ThreadPool(const ThreadPool&&) = delete; ThreadPool& operator=(const ThreadPool&&) = delete; private: bool stop_; // size_t pool_count_; std::vector<std::thread> workers_; //工作线程 std::queue<std::function<void()>> tasks_; //任务队列 std::mutex mtx_; std::condition_variable cv_; }; int main() { ThreadPool pool(4); // 创建4个线程 std::mutex mtx; // 提交普通任务 for (int i = 0; i < 8; ++i) { pool.submit([&mtx,i]() { { std::unique_lock<std::mutex> lock(mtx); std::cout << "task " << i << " run in thread: " << std::this_thread::get_id() << std::endl; } std::this_thread::sleep_for(std::chrono::seconds(1)); }); } // 提交带返回值任务 auto f = pool.submit([](int a, int b) { std::this_thread::sleep_for(std::chrono::seconds(2)); return a + b; }, 10, 20); std::cout << "result: " << f.get() << std::endl; return 0; }

