std::unique_lock 与 std::lock_guard 全面讲解
std::lock_guard 和 std::unique_lock 都是 C++ 标准库提供的RAII 风格互斥锁管理工具 ,核心目的是避免手动调用 lock()/unlock() 导致的锁泄漏(如异常时未解锁),但二者在灵活性、性能、适用场景上有显著差异。
一、核心共性:RAII 管理锁
无论 lock_guard 还是 unique_lock,都遵循 RAII 原则:
- 构造时:关联互斥锁(可选择立即加锁/延迟加锁);
- 析构时 :自动解锁(无需手动调用
unlock()); - 异常安全:即使临界区抛出异常,析构函数仍会执行,保证锁一定会释放,避免死锁。
二、核心差异对比(表格更清晰)
| 特性 | std::lock_guard | std::unique_lock |
|---|---|---|
| 灵活性 | 极低(极简封装) | 极高(全功能锁管理) |
| 锁的生命周期 | 与对象生命周期绑定(构造加锁,析构解锁) | 可手动控制(提前解锁、重新加锁) |
| 构造参数 | 仅支持 std::adopt_lock(接管已锁的锁) |
支持 std::defer_lock(延迟加锁)、std::adopt_lock(接管已锁)、std::try_to_lock(尝试加锁) |
| 手动解锁/加锁 | 不支持(无 unlock()/lock() 方法) |
支持(unlock()/lock()/try_lock()) |
| 所有权转移 | 不支持(不可拷贝、不可移动) | 支持(可移动,不可拷贝) |
| 性能 | 轻量级(无额外状态,几乎零开销) | 略重(维护锁的状态标记,如"是否持有锁") |
| 适用场景 | 简单的"加锁→执行→解锁"场景 | 复杂场景(延迟加锁、条件变量、超时加锁) |
三、std::lock_guard:极简的"一次性锁"
std::lock_guard 是轻量级、不可扩展的锁管理器,设计目标是"最简单的锁管理"。
1. 基本用法(核心场景)
cpp
#include <mutex>
#include <thread>
#include <iostream>
std::mutex m;
int count = 0;
void increment() {
// 构造时立即加锁,析构时自动解锁
std::lock_guard<std::mutex> lock(m);
count++; // 临界区
std::cout << count << std::endl;
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
return 0;
}
2. 关键特性解析
-
构造即加锁 :默认构造时会调用
mutex::lock(),无需手动操作; -
仅支持 std::adopt_lock :若锁已被手动锁定,可通过
adopt_lock让lock_guard接管(避免重复加锁):cppm.lock(); // 手动加锁 std::lock_guard<std::mutex> lock(m, std::adopt_lock); // 接管已锁的锁 -
不可手动控制 :没有
unlock()方法,锁必须等到lock_guard析构才释放; -
不可移动/拷贝:保证锁的所有权唯一,避免误操作。
3. 适用场景
- 临界区逻辑简单,无需提前解锁;
- 追求极致性能(无额外开销);
- 一次性加锁/解锁的场景(如简单的变量读写、函数内的临界区)。
四、std::unique_lock:灵活的"全能锁"
std::unique_lock 是功能完整、可灵活控制 的锁管理器,支持 lock_guard 的所有功能,还扩展了更多高级特性。
1. 核心用法1:延迟加锁(std::defer_lock)
最常用的高级特性,适合"多锁原子锁定"场景(如之前的 swap 示例):
cpp
// 延迟加锁:构造时不锁定,后续手动/批量锁定
std::unique_lock<std::mutex> lock_a(m1, std::defer_lock);
std::unique_lock<std::mutex> lock_b(m2, std::defer_lock);
std::lock(lock_a, lock_b); // 原子性锁定多个锁,避免死锁
// 临界区...
2. 核心用法2:手动解锁(提升并发效率)
可提前解锁,让其他线程更早访问资源:
cpp
std::unique_lock<std::mutex> lock(m);
// 操作1:必须加锁的核心逻辑
count++;
lock.unlock(); // 提前解锁,释放资源
// 操作2:无需加锁的耗时逻辑(如IO、计算)
std::this_thread::sleep_for(std::chrono::seconds(1));
lock.lock(); // 重新加锁(如需继续操作临界区)
count++;
// 析构时再次解锁
3. 核心用法3:尝试加锁(std::try_to_lock)
非阻塞式加锁,避免线程阻塞:
cpp
std::unique_lock<std::mutex> lock(m, std::try_to_lock);
if (lock.owns_lock()) { // 检查是否成功加锁
// 拿到锁,执行临界区
count++;
} else {
// 未拿到锁,执行备用逻辑
std::cout << "未拿到锁,跳过操作" << std::endl;
}
4. 核心用法4:配合条件变量(唯一适配)
std::condition_variable 的 wait() 方法仅接受 std::unique_lock(因为需要在等待时临时解锁,唤醒时重新加锁):
cpp
#include <condition_variable>
std::condition_variable cv;
std::mutex m;
bool ready = false;
void wait_for_ready() {
std::unique_lock<std::mutex> lock(m);
// 等待时自动解锁,唤醒时重新加锁
cv.wait(lock, []{ return ready; });
// 临界区...
}
五、选型原则:什么时候用哪个?
-
优先用 std::lock_guard:
- 场景:简单的临界区,无需提前解锁、延迟加锁;
- 理由:性能最优,代码更简洁,符合"极简主义"。
-
必须用 std::unique_lock:
- 场景1:需要延迟加锁(如多锁原子锁定);
- 场景2:需要手动解锁/重新加锁(如临界区拆分);
- 场景3:配合条件变量(
std::condition_variable); - 场景4:需要尝试加锁(非阻塞加锁)或超时加锁;
- 场景5:需要转移锁的所有权(如函数返回锁)。
std::call_once + std::once_flag 全解析(原理+用法+场景+坑点)
std::call_once(C++11 引入)结合 std::once_flag 是 C++ 标准库中实现多线程环境下函数仅执行一次 的核心工具,比手动加锁(如 std::mutex + 标志位)更安全、高效,且能避免"双重检查锁定"的经典坑点。
一、核心概念与底层原理
1. 基本定义
- std::once_flag:一个不可拷贝、不可移动的标记类型,用于标记"某个函数是否已经执行过",其内部存储了函数执行状态(未执行/已执行);
- std::call_once :模板函数,接收一个
std::once_flag引用和一个可调用对象(函数/lambda/函数对象),保证无论多少线程调用,该可调用对象仅被执行一次。
2. 底层原理
-
std::once_flag内部维护了一个原子状态变量(如std::atomic<bool>),标记函数是否已执行; -
std::call_once的核心逻辑:- 多个线程调用
call_once时,先原子检查once_flag的状态; - 若状态为"未执行",则其中一个线程会获取内部锁,执行目标函数,并将状态置为"已执行";
- 其他线程会阻塞等待,直到目标函数执行完成(或抛出异常);
- 若目标函数抛出异常,
once_flag状态会重置为"未执行",允许后续线程重新调用(这是与手动加锁最大的区别之一)。
- 多个线程调用
-
相比手动实现(
mutex + bool flag),call_once的优势:特性 std::call_once + once_flag 手动 mutex + bool 标志位 异常处理 函数抛异常时重置状态,允许重试 需手动处理异常,否则标志位错误 性能 执行后无锁开销(原子检查) 每次调用都需加锁/解锁 安全性 避免双重检查锁定的坑 易因内存序问题导致竞态 可移植性 跨平台兼容 需手动适配不同编译器内存模型
二、核心用法(从基础到进阶)
1. 基础用法:单例模式(最典型场景)
call_once 是实现线程安全单例的最优方式(替代双重检查锁定):
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <once_flag>
class Singleton {
private:
// 1. 定义once_flag(静态成员)
static std::once_flag init_flag;
// 2. 单例对象指针
static Singleton* instance;
// 私有构造函数(禁止外部实例化)
Singleton() {
std::cout << "Singleton 构造函数执行(仅执行一次)" << std::endl;
}
// 3. 初始化函数(要保证仅执行一次的逻辑)
static void init_instance() {
instance = new Singleton();
}
public:
// 4. 获取单例对象(多线程安全)
static Singleton* get_instance() {
// 核心:无论多少线程调用,init_instance仅执行一次
std::call_once(init_flag, init_instance);
return instance;
}
~Singleton() {
std::cout << "Singleton 析构" << std::endl;
}
void print() {
std::cout << "Singleton 实例地址:" << this << std::endl;
}
};
// 静态成员初始化
std::once_flag Singleton::init_flag;
Singleton* Singleton::instance = nullptr;
// 线程函数:调用单例
void thread_func(int id) {
std::cout << "线程" << id << " 获取单例" << std::endl;
Singleton* s = Singleton::get_instance();
s->print();
}
int main() {
// 创建5个线程,同时调用get_instance
std::thread t1(thread_func, 1);
std::thread t2(thread_func, 2);
std::thread t3(thread_func, 3);
std::thread t4(thread_func, 4);
std::thread t5(thread_func, 5);
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
return 0;
}
输出结果(核心特征):
线程1 获取单例
线程2 获取单例
Singleton 构造函数执行(仅执行一次)
Singleton 实例地址:0x7f8b9a4052a0
线程3 获取单例
Singleton 实例地址:0x7f8b9a4052a0
线程4 获取单例
Singleton 实例地址:0x7f8b9a4052a0
线程5 获取单例
Singleton 实例地址:0x7f8b9a4052a0
- 无论多少线程调用,构造函数仅执行一次;
- 所有线程获取的是同一个实例地址。
2. 基础用法:普通函数仅执行一次
cpp
#include <iostream>
#include <thread>
#include <once_flag>
std::once_flag flag;
void do_something() {
std::cout << "do_something 执行(仅一次)" << std::endl;
}
void thread_task(int id) {
std::cout << "线程" << id << " 调用call_once" << std::endl;
std::call_once(flag, do_something);
}
int main() {
std::thread t1(thread_task, 1);
std::thread t2(thread_task, 2);
std::thread t3(thread_task, 3);
t1.join();
t2.join();
t3.join();
return 0;
}
输出:
线程1 调用call_once
线程2 调用call_once
do_something 执行(仅一次)
线程3 调用call_once
3. 进阶用法:处理函数抛出异常的场景
若 call_once 调用的函数抛出异常,once_flag 会重置,允许后续线程重新调用:
cpp
#include <iostream>
#include <thread>
#include <once_flag>
#include <stdexcept>
std::once_flag flag;
int call_count = 0; // 记录函数调用次数
void risky_function() {
call_count++;
std::cout << "risky_function 第" << call_count << "次调用" << std::endl;
// 第一次调用抛异常,第二次调用正常执行
if (call_count == 1) {
throw std::runtime_error("第一次调用失败");
}
}
void thread_task(int id) {
try {
std::cout << "线程" << id << " 尝试调用risky_function" << std::endl;
std::call_once(flag, risky_function);
std::cout << "线程" << id << " 调用成功" << std::endl;
} catch (const std::exception& e) {
std::cout << "线程" << id << " 捕获异常:" << e.what() << std::endl;
}
}
int main() {
// 第一个线程调用,函数抛异常
std::thread t1(thread_task, 1);
t1.join();
// 第二个线程调用,函数正常执行
std::thread t2(thread_task, 2);
t2.join();
// 第三个线程调用,函数已执行过,不再调用
std::thread t3(thread_task, 3);
t3.join();
return 0;
}
输出:
线程1 尝试调用risky_function
risky_function 第1次调用
线程1 捕获异常:第一次调用失败
线程2 尝试调用risky_function
risky_function 第2次调用
线程2 调用成功
线程3 尝试调用risky_function
线程3 调用成功
- 关键点:第一次调用抛异常后,
once_flag未标记为"已执行",第二个线程可重新调用; - 第二次调用成功后,
once_flag标记为"已执行",第三个线程不再调用。
4. 进阶用法:传递参数给可调用对象
std::call_once 支持向目标函数传递参数(完美转发):
cpp
#include <iostream>
#include <thread>
#include <once_flag>
#include <string>
std::once_flag flag;
void print_info(const std::string& msg, int num) {
std::cout << "msg: " << msg << ", num: " << num << std::endl;
}
void thread_task() {
// 传递参数给print_info
std::call_once(flag, print_info, "仅执行一次的信息", 100);
}
int main() {
std::thread t1(thread_task);
std::thread t2(thread_task);
t1.join();
t2.join();
return 0;
}
输出:
msg: 仅执行一次的信息, num: 100
三、关键注意事项(避坑指南)
1. std::once_flag 的特性
-
不可拷贝/不可移动 :
once_flag没有拷贝构造函数和移动构造函数,只能通过引用传递给call_once;cpp// 错误:拷贝once_flag(编译失败) std::once_flag flag1; std::once_flag flag2 = flag1; // 正确:传递引用 std::call_once(flag1, do_something); -
生命周期 :
once_flag的生命周期必须覆盖所有调用call_once的线程,否则会导致未定义行为(如局部once_flag被销毁后,线程仍调用call_once);cpp// 错误示例:局部once_flag,线程可能在flag销毁后调用call_once void bad_func() { std::once_flag flag; std::thread t([&]() { std::call_once(flag, do_something); }); t.detach(); // 线程分离,可能在flag销毁后执行 }
2. 避免重复定义 once_flag
若 once_flag 是局部静态变量,需注意:C++11 后局部静态变量的初始化是线程安全的,但结合 call_once 时仍需保证 once_flag 唯一:
cpp
// 不推荐:局部静态once_flag(虽可行,但不如全局/类静态清晰)
void func() {
static std::once_flag flag;
std::call_once(flag, do_something);
}
3. 与 std::mutex 的选择
- 若仅需"函数执行一次":优先用
call_once + once_flag,更简洁、安全; - 若需"保护一段临界区,允许多次执行但互斥":用
std::mutex; - 若需"初始化后只读,且仅初始化一次":
call_once是最优解。
4. 性能考量
call_once在函数执行前:需要原子检查 + 可能的锁竞争(一次);- 函数执行后:仅需原子检查(无锁开销),性能接近直接读取原子变量;
- 相比手动
mutex + bool:执行后无解锁开销,性能更优。
四、典型应用场景
- 线程安全单例模式:替代双重检查锁定(DCLP),避免内存序问题;
- 全局/静态资源初始化:如配置文件加载、数据库连接初始化、日志系统初始化;
- 一次性初始化逻辑:如注册回调函数、加载动态库、初始化硬件资源;
- 异常安全的一次性执行:函数可能抛异常时,无需手动重置标志位。
总结(核心要点回顾)
- 核心功能 :
std::call_once结合std::once_flag保证多线程下函数仅执行一次,异常时可重试; - 核心优势:比手动加锁更安全(避免DCLP坑)、更高效(执行后无锁开销)、异常友好;
- 关键规则 :
once_flag不可拷贝/移动,生命周期需覆盖所有调用线程;- 函数抛异常时,
once_flag重置,允许后续线程重新调用; - 优先用于"一次性初始化"场景,而非通用互斥。
std::async 与 std::future 全解析(原理+用法+场景+坑点)
std::async(C++11 引入)是 C++ 标准库中异步执行任务 的核心工具,std::future 则是接收异步任务结果的"未来对象"------两者结合,可轻松实现"后台执行任务 + 主线程按需获取结果",是多线程编程中替代手动创建 std::thread 的更简洁、灵活的方案。
一、核心概念与底层原理
1. 基本定义
| 组件 | 核心作用 |
|---|---|
std::future<T> |
一个"未来对象",用于获取异步任务的结果 (或异常),T 是任务返回值类型; 若任务无返回值,用 std::future<void>。 |
std::async |
模板函数,用于启动异步任务 ,返回一个 std::future 对象,通过该对象可获取任务结果。 |
2. 底层原理
std::async的本质:封装了"线程创建 + 任务执行 + 结果存储 + 同步等待"的逻辑,底层依赖 C++ 标准库的线程池(或直接创建新线程,取决于启动策略);std::future的本质:一个"结果容器",内部维护了任务的状态(未完成/已完成/异常),并提供同步接口(如get())等待任务完成并获取结果;
3. std::async 的启动策略
std::async 支持显式指定任务执行方式(第一个参数),核心有两种策略:
| 策略常量 | 执行方式 |
|---|---|
std::launch::async |
强制创建新线程执行任务,任务立即异步启动; |
std::launch::deferred |
延迟执行任务:直到调用 future.get()/future.wait() 时,才在当前线程(调用者线程)同步执行; |
| 默认策略(不指定) | 编译器自动选择(通常是 `std::launch::async |
二、核心用法(从基础到进阶)
1. 基础用法:异步执行无参函数,获取返回值
cpp
#include <iostream>
#include <future> // 必须包含头文件
#include <chrono>
// 模拟耗时任务:返回一个整数
int long_task() {
std::cout << "异步任务开始执行(线程ID:" << std::this_thread::get_id() << ")" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时2秒
return 100;
}
int main() {
std::cout << "主线程开始(线程ID:" << std::this_thread::get_id() << ")" << std::endl;
// 1. 启动异步任务,默认策略
std::future<int> fut = std::async(long_task);
// 2. 主线程继续执行其他逻辑(无需等待任务完成)
std::cout << "主线程执行其他操作..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
// 3. 按需获取任务结果:若任务未完成,主线程阻塞至任务结束
int result = fut.get(); // 注意:get()只能调用一次!
std::cout << "异步任务结果:" << result << std::endl;
return 0;
}
输出结果:
主线程开始(线程ID:140709267966784)
主线程执行其他操作...
异步任务开始执行(线程ID:140709250088704)
异步任务结果:100
- 核心特征:异步任务在新线程执行,主线程可并行处理其他逻辑,
get()时阻塞获取结果; - 关键注意:
future.get()只能调用一次 ,调用后future进入"无效状态",再次调用会触发未定义行为。
2. 基础用法:指定启动策略
(1)强制异步执行(std::launch::async)
cpp
// 显式指定异步策略,强制创建新线程
std::future<int> fut = std::async(std::launch::async, long_task);
- 任务会立即在新线程启动,即使主线程不调用
get(),任务也会执行(直到完成)。
(2)延迟执行(std::launch::deferred)
cpp
// 显式指定延迟策略,任务不会立即执行
std::future<int> fut = std::async(std::launch::deferred, long_task);
std::cout << "主线程执行1秒..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
// 调用get()时,任务在主线程同步执行(无新线程)
int result = fut.get();
std::cout << "异步任务结果:" << result << std::endl;
输出结果(注意线程ID相同):
主线程开始(线程ID:140709267966784)
主线程执行1秒...
异步任务开始执行(线程ID:140709267966784)
异步任务结果:100
- 核心特征:延迟策略下,任务无新线程,
get()时在主线程同步执行,适合"按需执行"的轻量任务。
3. 进阶用法:传递参数给异步任务
std::async 支持向任务函数传递任意参数(完美转发),语法与 std::bind 一致:
cpp
#include <iostream>
#include <future>
#include <string>
// 带参数的任务函数
std::string greet(const std::string& name, int age) {
return "Hello, " + name + "! Your age is " + std::to_string(age);
}
int main() {
// 传递参数给异步任务
std::future<std::string> fut = std::async(greet, "Alice", 25);
// 获取结果
std::string result = fut.get();
std::cout << result << std::endl; // 输出:Hello, Alice! Your age is 25
return 0;
}
4. 进阶用法:处理异步任务的异常
若异步任务抛出异常,异常会被存储到 std::future 中,调用 get() 时会重新抛出该异常(主线程可捕获):
cpp
#include <iostream>
#include <future>
#include <stdexcept>
// 会抛异常的异步任务
int risky_task() {
throw std::runtime_error("异步任务执行失败!");
return 0; // 不会执行
}
int main() {
std::future<int> fut = std::async(risky_task);
// 捕获异步任务的异常
try {
int result = fut.get();
std::cout << "结果:" << result << std::endl;
} catch (const std::exception& e) {
std::cout << "捕获异常:" << e.what() << std::endl;
}
return 0;
}
输出:
捕获异常:异步任务执行失败!
- 核心规则:异步任务的异常不会直接崩溃程序,而是被
future捕获,主线程通过try-catch处理即可。
5. 进阶用法:std::future 的其他核心接口
除了 get(),std::future 还提供以下常用接口:
| 接口 | 作用 |
|---|---|
wait() |
阻塞等待任务完成,但不获取结果(可多次调用); |
wait_for(d) |
阻塞等待指定时长(如 std::chrono::seconds(1)),返回等待状态: - std::future_status::ready:任务完成; - std::future_status::timeout:超时; - std::future_status::deferred:任务延迟执行。 |
valid() |
判断 future 是否有效(未调用 get() 且任务未取消); |
示例:wait_for 非阻塞等待
cpp
#include <iostream>
#include <future>
#include <chrono>
int long_task() {
std::this_thread::sleep_for(std::chrono::seconds(3));
return 100;
}
int main() {
std::future<int> fut = std::async(std::launch::async, long_task);
// 非阻塞等待:最多等2秒
std::future_status status = fut.wait_for(std::chrono::seconds(2));
if (status == std::future_status::ready) {
std::cout << "任务已完成,结果:" << fut.get() << std::endl;
} else if (status == std::future_status::timeout) {
std::cout << "等待超时,任务仍在执行..." << std::endl;
} else if (status == std::future_status::deferred) {
std::cout << "任务延迟执行,调用get()才会执行" << std::endl;
}
// 继续等待任务完成
int result = fut.get();
std::cout << "最终结果:" << result << std::endl;
return 0;
}
输出:
等待超时,任务仍在执行...
最终结果:100
6. 进阶用法:std::shared_future(共享结果)
std::future 是"独占式"的(get() 只能调用一次),而 std::shared_future 是"共享式"的,允许多个线程多次获取同一任务的结果:
cpp
#include <iostream>
#include <future>
#include <thread>
int task() {
return 100;
}
// 多个线程获取共享结果
void thread_func(std::shared_future<int> sf, int id) {
int result = sf.get(); // 可多次调用
std::cout << "线程" << id << " 获取结果:" << result << std::endl;
}
int main() {
std::future<int> fut = std::async(task);
// 将future转换为shared_future(future会失效)
std::shared_future<int> sf = fut.share();
// 多个线程共享结果
std::thread t1(thread_func, sf, 1);
std::thread t2(thread_func, sf, 2);
t1.join();
t2.join();
return 0;
}
输出:
线程1 获取结果:100
线程2 获取结果:100
- 核心规则:
std::shared_future可拷贝,多个对象共享同一任务结果,get()可多次调用。
三、关键注意事项(避坑指南)
1. std::future 的生命周期陷阱
-
未调用 get()/wait() 的坑 :若
std::async返回的future是局部变量,其析构函数会阻塞等待任务完成 (即使未调用get()):cppvoid bad_func() { // fut是局部变量,函数结束时析构,会阻塞等待任务完成 std::future<int> fut = std::async(std::launch::async, long_task); // 函数结束,fut析构,主线程阻塞2秒 }解决:若无需等待结果,需将
future存储到全局/动态内存,或使用std::move转移所有权。
2. 默认策略的不确定性
- 默认策略(
std::launch::async | std::launch::deferred)下,编译器可能选择延迟执行,导致任务在主线程同步执行(无新线程); - 若需强制异步,必须显式指定
std::launch::async。
3. get() 只能调用一次
std::future::get()调用后,future会释放结果资源,变为"无效状态",再次调用会触发未定义行为;- 若需多次获取结果,使用
std::shared_future。
4. 避免异步任务捕获局部变量的引用
-
异步任务若捕获主线程局部变量的引用,可能导致"悬垂引用"(主线程变量销毁后,任务仍访问):
cppvoid bad_example() { int x = 10; // 危险:捕获x的引用,若任务延迟执行,x可能已销毁 std::future<int> fut = std::async([&]() { return x * 2; }); // x超出作用域销毁 int res = fut.get(); // 未定义行为:访问已销毁的x }解决:捕获变量的值(而非引用),或确保变量生命周期覆盖任务执行。
5. 与 std::thread 的选择
| 特性 | std::async + std::future | std::thread |
|---|---|---|
| 结果获取 | 内置支持(future.get()) | 需手动通过全局变量/指针传递结果 |
| 异常处理 | 自动捕获,get()时抛出 | 未捕获的异常会导致程序崩溃(std::terminate) |
| 线程管理 | 自动管理(无需手动join) | 必须手动join/detach,否则析构崩溃 |
| 灵活性 | 支持延迟执行/线程池 | 仅能创建新线程 |
| 适用场景 | 简单异步任务,需获取结果 | 复杂线程控制(如线程分离、优先级) |
四、典型应用场景
- 后台执行耗时任务:如文件读写、网络请求、数据计算,主线程无需阻塞,按需获取结果;
- 并行计算 :拆分任务为多个异步子任务,通过
future获取各子任务结果后汇总; - 延迟执行轻量任务 :用
std::launch::deferred实现"懒加载",仅在需要时执行任务; - 异常安全的异步操作 :通过
try-catch捕获异步任务的异常,避免程序崩溃。
总结(核心要点回顾)
- 核心功能 :
std::async启动异步任务,返回std::future用于获取结果;std::future是"未来对象",通过get()阻塞获取结果(或wait()仅等待);
- 关键策略 :
std::launch::async:强制新线程异步执行;std::launch::deferred:延迟执行,get()时同步执行;
- 核心坑点 :
future局部析构会阻塞等待任务完成;get()只能调用一次,共享结果用std::shared_future;- 默认策略可能导致同步执行,需显式指定异步策略。