文章目录
基础
1. 线程创建与管理
- C++11 通过 头文件提供了跨平台的线程支持
#include <iostream>
#include <thread>
#include <chrono>
void threadFunction(int id) {
std::cout << "线程 " << id << " 开始执行" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "线程 " << id << " 结束执行" << std::endl;
}
int main() {
std::cout << "主线程开始,创建3个子线程" << std::endl;
// 创建并启动线程
std::thread t1(threadFunction, 1);
std::thread t2(threadFunction, 2);
std::thread t3(threadFunction, 3);
// 等待线程完成
t1.join();
t2.join();
t3.join();
std::cout << "所有线程执行完毕" << std::endl;
return 0;
}
2. 多种线程创建方式
// 1. 函数指针
void normalFunction() { /* ... */ }
std::thread t1(normalFunction);
// 2. Lambda表达式(推荐,简洁)
std::thread t2([]() {
std::cout << "Lambda线程" << std::endl;
});
// 3. 函数对象(仿函数)
struct MyFunctor {
void operator()() { /* ... */ }
};
std::thread t3(MyFunctor{});
// 4. 成员函数
class MyClass {
public:
void memberFunction() { /* ... */ }
};
MyClass obj;
std::thread t4(&MyClass::memberFunction, &obj);
3. 线程生命周期管理
- 如果不调用 join() 或 detach(),thread 对象析构时会调用 std::terminate()终止程序.
std::thread t([]{
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "后台线程完成" << std::endl;
});
// 必须在销毁thread对象前调用join()或detach()
if (t.joinable()) {
t.join(); // 等待完成
// t.detach(); // 或分离到后台
}
- 线程移动语义:std::thread是不可拷贝的,但支持移动语义。这允许线程所有权的转移。
#include <iostream>
#include <thread>
#include <vector>
void worker(int id) {
std::cout << "Worker " << id << " running on thread: "
<< std::this_thread::get_id() << std::endl;
}
int main() {
// 1. 从临时对象构造(隐式移动)
std::thread t1([]{
std::cout << "Thread from lambda\n";
});
// 2. 移动构造:转移所有权
std::thread t2 = std::move(t1);
// 此时 t1 不再拥有线程(joinable() == false)
std::cout << "t1 joinable: " << t1.joinable() << std::endl; // 0
std::cout << "t2 joinable: " << t2.joinable() << std::endl; // 1
// 3. 移动赋值
std::thread t3;
t3 = std::move(t2); // 转移所有权到 t3
// 注意:如果 t3 已有线程,会先调用 std::terminate()
t3.join();
// 4. 在容器中存储线程
std::vector<std::thread> threads;
for (int i = 0; i < 3; ++i) {
// emplace_back 直接构造
threads.emplace_back(worker, i);
// 或者 push_back 移动
// std::thread t(worker, i);
// threads.push_back(std::move(t));
}
for (auto& t : threads) {
t.join();
}
return 0;
}
- 线程局部存储:每个线程都有自己的一个局部变量
- 延迟初始化:每个线程第一次访问 thread_local变量时才初始化
- 线程结束时销毁:变量在线程退出时按创建顺序的逆序销毁
- 每个线程独立:每个线程都有自己独立的实例
// 普通全局变量 - 所有线程共享同一个
int global_counter = 0;
// thread_local 变量 - 每个线程有独立的副本
thread_local int thread_counter = 0;
void increment() {
for (int i = 0; i < 5; ++i) {
global_counter++; // 修改共享的全局变量
thread_counter++; // 修改本线程的独立副本
std::cout << "Thread " << std::this_thread::get_id()
<< ": global = " << global_counter
<< ", thread_local = " << thread_counter << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
- 线程异常处理:线程中的异常不会自动传播到创建线程的上下文中,需要在线程内部处理。
4. 参数传递
- 值传递:参数被拷贝到新线程的上下文。确保线程拥有独立的数据,避免悬挂引用。
- 引用传递:必须使用 std::ref或 std::cref进行包装,否则会尝试拷贝引用本身(非法)。
void modify(int& val) { val = 100; }
int main() {
int x = 10;
// std::thread t(modify, x); // 错误!试图拷贝int&
std::thread t(modify, std::ref(x)); // 正确:传递引用包装器
t.join();
std::cout << x << std::endl; // 输出 100
}
- 指针传递:传递指针(地址)本身是值传递(拷贝了指针值)。需确保指针所指向对象的生命周期覆盖线程的执行,否则会访问已释放内存。
- 移动语义:对于不可拷贝但可移动的对象(如 std::unique_ptr, std::future),使用 std::move。
void sink(std::unique_ptr<int> up) {}
int main() {
auto up = std::make_unique<int>(99);
std::thread t(sink, std::move(up)); // 所有权转移到线程内部
t.join();
// 此时 up 为空
}
同步原语
互斥锁
原理:给共享数据加一把锁,同一时间只有一个线程能访问,其他线程排队等着。
- std::mutex
- 最基础、最常用的独占互斥锁。
- 同一时间只能一个线程持有
- 拿不到就阻塞休眠(不占 CPU)
- 简单、安全、通用
- std::recursive_mutex 递归锁
- 允许同一个线程多次加锁。
- 适用场景:
- 函数 A 加锁 → 调用函数 B,B 也加同一个锁。
- 普通 mutex 会死锁,递归锁不会。
- std::timed_mutex / std::recursive_timed_mutex
- 带超时的锁。
- 拿不到锁不会死等,可以等一段时间就放弃。
- 用来破坏死锁的 “不可抢占” 条件。
std::timed_mutex mtx;
// 等 100ms,拿不到就拉倒
if (mtx.try_lock_for(std::chrono::milliseconds(100))) {
// 拿到了
} else {
// 没拿到
}
- std::shared_mutex 读写锁(C++17)
- 读锁(shared lock):可以多人同时读
- 写锁(unique lock):只能一个人写,且写的时候不能读
适用场景:读多写少,比如配置、缓存、日志。
std::shared_mutex mtx;
// 读:加共享锁
mtx.lock_shared();
// 写:加独占锁
mtx.lock();
#include <iostream>
#include <thread>
#include <mutex>
#include <shared_mutex>
#include <vector>
#include <chrono>
// 全局数据,读多写少
int g_data = 0;
// 读写锁
std::shared_mutex g_rw_mutex;
// 读者:多个可以同时读
void reader(int id) {
for (int i = 0; i < 3; ++i) {
// 🔥 读锁:shared_lock
std::shared_lock<std::shared_mutex> lock(g_rw_mutex);
std::cout << "读者 " << id << " 读到: " << g_data << '\n';
// 模拟读耗时,多个读者可以并行
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
}
// 写者:同时只能有一个在写
void writer(int id) {
for (int i = 0; i < 2; ++i) {
// 🔥 写锁:unique_lock / lock_guard
std::unique_lock<std::shared_mutex> lock(g_rw_mutex);
g_data++;
std::cout << "====== 写者 " << id << " 修改为: " << g_data << " ======\n";
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
int main() {
// 2 个写者
std::thread w1(writer, 1);
std::thread w2(writer, 2);
// 5 个读者
std::thread r1(reader, 1);
std::thread r2(reader, 2);
std::thread r3(reader, 3);
std::thread r4(reader, 4);
std::thread r5(reader, 5);
w1.join(); w2.join();
r1.join(); r2.join(); r3.join(); r4.join(); r5.join();
return 0;
}
- 锁的关键特性(必须懂)
- 原子性:加锁、解锁本身是原子操作,CPU 指令保证不会被打断。
- 可见性:解锁线程的所有修改,对随后加锁的线程全部可见。(相当于内存屏障)。unlock 之前,线程对内存的所有修改,必须刷回内存;lock 之后,线程必须从内存重新读,不能用旧缓存。
- 有序性“”加锁 / 解锁会禁止编译器与 CPU 乱序重排。在 lock 之后的代码,不能重排到 lock 之前;
在 unlock 之前的代码,不能重排到 unlock 之后。 - 等待队列:锁内部维护一个等待队列,线程拿不到锁就进入队列休眠,不浪费 CPU。每个 mutex 内部都有一个等待队列(wait queue),而不是在拿不到锁的情况下一直循环等待,浪费cpu
- 线程 A 拿到锁
- 线程 B 来 lock,发现锁被占
- 操作系统把线程 B 从运行队列移到锁的等待队列
- 线程 B 进入休眠状态,完全不占 CPU
- 线程 A unlock
- 操作系统从等待队列唤醒一个线程,让它去拿锁
锁管理器
手动 lock/unlock 会因为异常导致死锁,C++ 必须用 RAII
std::lock_guard
它是 C++ 为 std::mutex 做的「自动锁开关」,利用 RAII 机制:构造时自动加锁,离开作用域时自动解锁。
你完全不用手动写 .lock() 和 .unlock(),从根源避免忘记解锁导致死锁。
#include <mutex>
std::mutex mtx;
int shared = 0;
void good_func() {
// 🔥 构造时:自动调用 mtx.lock()
std::lock_guard<std::mutex> lock(mtx);
shared++;
// 中间随便抛异常、return、break 都无所谓
}
// 🔥 离开大括号作用域:lock_guard 析构,自动调用 mtx.unlock()
很少用的参数:std::adopt_lock:你已经手动锁了 mutex,想让 lock_guard 接管解锁,不再重复加锁。
mtx.lock(); // 手动先锁
std::lock_guard lock(mtx, std::adopt_lock); // 只接管,不再加锁
std::unique_lock
- std::unique_lock = 加强版的 std::lock_guard
- 保留核心优点:RAII 自动解锁(离开作用域必解锁,绝不死锁)
- 新增核心能力:手动控制锁(可以手动加锁、解锁、尝试加锁、超时加锁)
- 默认构造:创建时自动加锁,离开作用域自动解锁。
#include <mutex>
std::mutex mtx;
void func() {
// 1. 自动调用 mtx.lock()
std::unique_lock<std::mutex> lock(mtx);
// 临界区:操作共享数据
shared_data++;
} // 2. 自动调用 mtx.unlock(),绝对安全
- std::defer_lock → 只绑定,不加锁(最常用)
// 不自动加锁!只是把 mtx 和 lock 绑定
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
// 想加锁时手动加
lock.lock();
// 想解锁时手动解
lock.unlock();
- std::try_to_lock → 尝试加锁,不阻塞。拿不到锁直接返回,不卡死线程。
std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
if (lock.owns_lock()) {
// 成功拿到锁
} else {
// 没拿到,去做别的事
}
- std::adopt_lock → 接管已加锁的 mutex。你已经手动加锁了,让 unique_lock 负责自动解锁:
mtx.lock(); // 手动先加锁
// 告诉 unique_lock:锁已经加好了,你帮我解锁就行
std::unique_lock<std::mutex> lock(mtx, std::adopt_lock);
- try_lock():非阻塞尝试加锁
- try_lock_for(超时时间):超时尝试加锁
- owns_lock():判断是否持有锁
std::scoped_lock(C++17)
std::scoped_lock是 C++17 引入的 RAII 锁封装器,用于管理多个互斥量的锁定和解锁。它是对 std::lock_guard的增强,支持同时锁定多个互斥量而不会死锁。
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
std::mutex m1, m2, m3;
void using_lock_guard() {
// 旧方式:可能死锁!
// std::lock_guard<std::mutex> lk1(m1);
// std::lock_guard<std::mutex> lk2(m2); // 另一个线程可能以相反顺序锁定
// 正确方式(C++11/C++14):
std::lock(m1, m2, m3); // 使用 std::lock 避免死锁
std::lock_guard<std::mutex> lk1(m1, std::adopt_lock);
std::lock_guard<std::mutex> lk2(m2, std::adopt_lock);
std::lock_guard<std::mutex> lk3(m3, std::adopt_lock);
std::cout << "lock_guard: locked 3 mutexes\n";
// 离开作用域自动解锁
}
void using_scoped_lock() {
// C++17 新方式:一行搞定
std::scoped_lock lock(m1, m2, m3);
std::cout << "scoped_lock: locked 3 mutexes\n";
// 离开作用域自动解锁
}
void compare() {
std::thread t1([]{
for (int i = 0; i < 100; ++i) {
using_lock_guard();
}
});
std::thread t2([]{
for (int i = 0; i < 100; ++i) {
using_scoped_lock();
}
});
t1.join();
t2.join();
}
int main() {
compare();
return 0;
}
死锁
死锁 4 条件(破坏一个就解决):
- 互斥(锁独占)
- 持有并等待(拿着锁等别的锁)
- 不可抢占(锁不能抢)
- 循环等待(A 等 B,B 等 A)
工程解法:固定加锁顺序、避免嵌套锁、用超时锁。
条件变量
线程需要等 “某个事情发生” 才能继续干活。如果没有条件变量,你只能这么写:
while (队列是空的) {
// 啥也不干,一直循环检查
}
这种写法叫轮询。问题非常大:线程一直在跑,CPU 占用 100%;浪费电量、浪费性能;系统变卡。
条件变量就是解决这个问题的。它的作用只有一个:。让线程 “休眠等待”,直到别的线程通知它 “条件满足了”,它再醒来干活。休眠时,线程完全不占用 CPU,操作系统会把它挂起。
- 条件变量必须搭配互斥锁(mutex),因为:
- 你要检查的 “条件”(比如队列是否为空)是共享变量
- 多个线程会同时读写这个条件
- 所以必须用锁保护,否则会出现数据竞争
- 条件变量只能用 std::unique_lock
- cv.wait(你的unique_lock锁, 条件判断函数);wait 会顺序执行以下 3 步,完全自动,你不用管:
- 自动解锁
- 线程进入休眠,直到有人调用 notify_one() 或 notify_all()。(通知 notify 不需要加锁)
- 被唤醒后,自动重新加锁
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
// 共享队列
std::queue<int> q;
// 互斥锁,保护队列
std::mutex mtx;
// 条件变量,用于等待队列非空
std::condition_variable cv;
// 消费者线程
void consumer() {
while (true) {
// 消费者必须先加锁
std::unique_lock<std::mutex> lock(mtx);
// 等待:队列不为空
// 如果为空,wait 会自动解锁并休眠
// 被唤醒后,自动重新加锁
while (q.empty()) {
cv.wait(lock);
}
// 走到这里:
// 1. 队列一定不为空
// 2. 锁已经被当前线程持有
int value = q.front();
q.pop();
// 用完数据,解锁
lock.unlock();
// 打印不需要锁
std::cout << "消费者拿到数据:" << value << std::endl;
// -1 代表结束
if (value == -1) {
break;
}
}
}
// 生产者线程
void producer() {
// 生产 5 个数据
for (int i = 1; i <= 5; ++i) {
// 修改共享数据必须加锁
std::lock_guard<std::mutex> lock(mtx);
// 往队列放数据 → 条件发生变化
q.push(i);
// 锁自动释放
}
// 通知消费者:数据来了
cv.notify_one();
// 模拟耗时
std::this_thread::sleep_for(std::chrono::seconds(1));
// 最后发送结束信号
{
std::lock_guard<std::mutex> lock(mtx);
q.push(-1);
}
cv.notify_one();
}
int main() {
std::thread t_consumer(consumer);
std::thread t_producer(producer);
t_consumer.join();
t_producer.join();
return 0;
}
原子操作
原子操作是不可分割的操作,要么完全执行,要么完全不执行,不会被其他线程的操作打断。核心特性:
- 不可分割性
// 原子操作是硬件保证的
std::atomic<int> x(0);
// 这些操作是原子的:
x.load(); // 读
x.store(42); // 写
x.fetch_add(1); // 读-修改-写
x.exchange(100); // 交换
x.compare_exchange_weak(expected, desired); // 比较并交换
- 内存序
std::atomic<int> data(0);
std::atomic<bool> ready(false);
// 线程1:生产者
data.store(42, std::memory_order_relaxed);
ready.store(true, std::memory_order_release); // 释放屏障
// 线程2:消费者
while (!ready.load(std::memory_order_acquire)) {} // 获取屏障
int val = data.load(std::memory_order_relaxed);
// 这里保证看到 data = 42
无锁编程
不使用互斥锁来实现线程安全,通常依赖原子操作和CAS。CAS(Compare-And-Swap)是无锁编程的核心操作。
Future/Promise模式
多线程之间,怎么把一个 “结果” 从线程 A 传给线程 B?
以前只能用:全局变量、锁、条件变量。很麻烦,很容易写错。C++ 给了一个高级、安全、自带同步的工具:
- promise 是 “放数据的人”
- future 是 “拿数据的人”
它们天生线程安全,不需要锁,不需要条件变量。
std::promise 生产者:在某个线程里负责存入一个值或者抛出一个异常
std::future 消费者:在另一个线程里负责等待结果。结果没好就阻塞休眠,结果好了就拿到值。
它们是一对一对出现的。
#include <iostream>
#include <thread>
#include <future>
// 子线程:生产者,用 promise
func1(std::promise<int> p) {
// 计算...
int result = 999;
// 把结果放进去
p.set_value(result);
}
// 主线程:消费者,用 future
int main() {
// 创建一对:promise 和 future
std::promise<int> p;
std::future<int> f = p.get_future();
// 把 promise 交给子线程
std::thread t(func1, std::move(p));
// 等待结果(阻塞等,不浪费CPU)
int value = f.get();
std::cout << value << std::endl; // 输出 999
t.join();
return 0;
}
std::future 的重要规则:
- get() 只能调用一次
- 拿完就失效了
- 调用 get() 如果结果没好,会阻塞等待
- 不需要锁,线程安全
- 一个 future 只能被一个线程使用
std::future的用法
- wait():等待结果,但不获取。
- get():等待并获取结果。只能调用一次,如果异步函数抛出异常,get会把异常传递出来。
auto status = f.wait_for(std::chrono::seconds(1));等待一段时间,看是否就绪:- std::future_status::ready:结果好了
- std::future_status::timeout:超时了
- std::future_status::deferred:延迟执行
- valid():检查是否有效,如果get过,就是false,无效了。
- share():把普通future转为shared_future
std::shared_future 是什么?
- 可以被多个线程共享、多次 get () 的 future。
- 可以拷贝
- 可以给多个线程
- 每个线程都能调用 get()
- 大家拿到同一个结果
std::future<int> f = p.get_future();
std::shared_future<int> sf = f.share();
异常传播机制
子线程抛出异常,子线程捕获
func1(std::promise<int> p) {
try {
// 子线程出错
throw std::runtime_error("子线程爆炸了");
} catch (...) {
// 把异常放进 promise
p.set_exception(std::current_exception());
}
}
int main() {
std::promise<int> p;
std::future<int> f = p.get_future();
std::thread t(func1, std::move(p));
try {
// 主线程 get() 的时候,异常会被抛出来!
int v = f.get();
} catch (const std::exception& e) {
std::cout << e.what() << std::endl;
// 输出:子线程爆炸了
}
t.join();
return 0;
}
异步执行
- std::async: 异步执行。让一个函数 “在后台异步运行” 的工具。不用自己创建 thread,不用自己写 promise,自动给你返回 future 拿结果。
#include <iostream>
#include <future>
using namespace std;
int func() {
return 100;
}
int main() {
// 异步执行 func()
future<int> f = async(func);
// 等待执行完,拿结果
int res = f.get();
cout << res << endl; // 输出 100
}
- std::async 的两种启动策略
- std::launch::async:立即开新线程,立刻后台运行
async(launch::async, func); - std::launch::deferred:不开线程,延迟执行,直到你调用 get () 才跑!
async(launch::deferred, func);
打包任务
std::packaged_task 就是一个包装器。把一个普通函数,包装成 “可以异步执行、并且能通过 future 获取结果 / 异常” 的任务。三种使用方式:
- 直接使用,顺序执行
packaged_task<int()> task([]{ return 123; });
task(); // 直接运行
auto f = task.get_future();
cout << f.get();
- 传给 std::thread(异步)
#include <iostream>
#include <future>
#include <thread>
using namespace std;
// 普通函数
int add(int a, int b) {
return a + b;
}
int main() {
// 1. 把函数包装成 packaged_task
packaged_task<int(int, int)> task(add);
// 2. 从任务里拿 future
future<int> f = task.get_future();
// 3. 把任务交给线程执行
thread t(std::move(task), 10, 20);
// 4. 拿结果
int res = f.get();
cout << res << endl; // 输出 30
t.join();
return 0;
}
- 传给线程池(最真实、最常用)。这才是 packaged_task 存在的真正意义!你可以把任务存起来、排队、交给线程池执行。async 做不到,promise 太麻烦。
// 线程池里放一堆任务
std::vector<std::packaged_task<int()>> tasks;
// 添加任务
std::packaged_task<int()> task([]{ return 100; });
tasks.push_back(std::move(task));
// 线程池取出执行
auto& t = tasks[0];
t();
注意:
- packaged_task 不能拷贝,只能移动(move)
- 必须用 std::move 传给线程
- 一个任务只能执行一次
- 执行完之后,任务就无效了
- 必须先 get_future (),再执行任务
- 异常会自动传给 future
线程池
- 线程池解决 3 个问题:
- 避免频繁创建 / 销毁线程的开销
- 控制同时运行的线程数量
- 任务来了直接执行,不用等线程创建
- 线程池内部长啥样?(结构超级简单)
- 一个标准线程池,只由 4 部分组成:
- 一批工作线程(固定数量,比如 4、8、16 个)
- 一个任务队列(存放要执行的函数 / 任务)
- 一把互斥锁(保护任务队列)
- 一个条件变量(线程等待任务时睡觉,不占 CPU)
- 线程数量怎么设?
(1)CPU 密集型任务(计算多):线程数 = CPU 核心数(或 +1)
IO 密集型任务(网络、文件、数据库):线程数可以多一些,比如 CPU 核心数 × 2 或更多
(2)任务队列:用 std::queue 存放任务,先进先出。
(3)为什么必须用条件变量?没有任务时,线程睡觉不占 CPU;任务来了,再叫醒。
(4)为什么必须用锁?多个线程同时取任务,队列是共享的,必须互斥。
(5)线程池销毁时做什么?- 标记停止
- 唤醒所有线程
- 等待所有线程执行完当前任务并退出避免内存泄漏、崩溃
同步工具
信号量
栅栏
门闩
并行算法
执行策略
并行STL算法
协程
线程安全设计
子线程函数参数传递
- 值传递:参数会被复制到新线程的独立空间
- 引用传递:需用 std::ref(或只读引用 std::cref)包装,否则会被当作值拷贝。
void modifyValue(int& value) {
value *= 2;
}
int main() {
int sharedValue = 42;
// 错误:std::thread t(modifyValue, sharedValue); 会尝试拷贝int&,编译失败
std::thread t(modifyValue, std::ref(sharedValue)); // 正确
t.join();
std::cout << "Modified value: " << sharedValue << std::endl; // 输出 84
return 0;
}
- 指针传递:传递的是地址的拷贝,但指向同一块内存。
- 移动语义传递:对于像 std::unique_ptr或大数据容器,可用 std::move转移所有权,避免拷贝。
void takeOwnership(std::unique_ptr<int> ptr) {
std::cout << "Value: " << *ptr << std::endl;
}
int main() {
auto uniquePtr = std::make_unique<int>(100);
// std::thread t(takeOwnership, uniquePtr); // 错误:unique_ptr不可拷贝
std::thread t(takeOwnership, std::move(uniquePtr)); // 正确:转移所有权
t.join();
// 此后uniquePtr不再拥有对象
return 0;
}
子线程获取返回值
线程函数本身不直接返回给调用者,需通过异步操作工具获取结果。
- std::async自动创建线程(或利用线程池)执行任务,并返回一个 std::future对象用于获取结果
#include <future>
int computeSquare(int n) {
return n * n;
}
int main() {
// 异步执行任务,std::launch::async确保在新线程中执行
std::future<int> result = std::async(std::launch::async, computeSquare, 12);
// ... 主线程可同时做其他工作 ...
int value = result.get(); // 获取结果(若未完成会阻塞等待)
std::cout << "The square is: " << value << std::endl; // 输出 144
return 0;
}
- std::packaged_task将可调用对象包装起来,允许您获取与之关联的 std::future。它本身不自动创建线程,需手动将其传递给 std::thread
#include <future>
int heavyComputation() {
std::this_thread::sleep_for(std::chrono::seconds(2));
return 42;
}
int main() {
// 包装任务
std::packaged_task<int()> task(heavyComputation);
// 获取与任务关联的future
std::future<int> result = task.get_future();
// 将任务移动到线程中执行
std::thread t(std::move(task));
t.detach(); // 或使用join()
std::cout << "Waiting for result..." << std::endl;
std::cout << "Result: " << result.get() << std::endl; // 阻塞直到获取结果
return 0;
}
- 通过 std::promise对象,在线程中任意位置显式设置返回值
#include <future>
void workerTask(std::promise<int>&& resultPromise, int a, int b) {
std::this_thread::sleep_for(std::chrono::seconds(1));
int result = a + b;
resultPromise.set_value(result); // 设置结果
}
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future(); // 获取future
std::thread t(workerTask, std::move(prom), 10, 20);
// ... 其他工作 ...
int sum = fut.get(); // 获取结果
std::cout << "Sum received: " << sum << std::endl; // 输出 30
t.join();
return 0;
}
- 异常处理:若在线程函数中抛出异常且未捕获,异常会被传递到 std::future的 get()方法处重新抛出
- std::future::get()的阻塞性:get()方法会阻塞调用线程,直到结果可用。确保在需要结果时才调用,或使用 wait_for/wait_until查询状态

1625

被折叠的 条评论
为什么被折叠?



