std::unique_lock 与 std::lock_guard

std::unique_lock 与 std::lock_guard 全面讲解

std::lock_guardstd::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_locklock_guard 接管(避免重复加锁):

    cpp 复制代码
    m.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_variablewait() 方法仅接受 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; }); 
    // 临界区...
}
五、选型原则:什么时候用哪个?
  1. 优先用 std::lock_guard

    • 场景:简单的临界区,无需提前解锁、延迟加锁;
    • 理由:性能最优,代码更简洁,符合"极简主义"。
  2. 必须用 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 的核心逻辑:

    1. 多个线程调用 call_once 时,先原子检查 once_flag 的状态;
    2. 若状态为"未执行",则其中一个线程会获取内部锁,执行目标函数,并将状态置为"已执行";
    3. 其他线程会阻塞等待,直到目标函数执行完成(或抛出异常);
    4. 若目标函数抛出异常,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:执行后无解锁开销,性能更优。

四、典型应用场景

  1. 线程安全单例模式:替代双重检查锁定(DCLP),避免内存序问题;
  2. 全局/静态资源初始化:如配置文件加载、数据库连接初始化、日志系统初始化;
  3. 一次性初始化逻辑:如注册回调函数、加载动态库、初始化硬件资源;
  4. 异常安全的一次性执行:函数可能抛异常时,无需手动重置标志位。

总结(核心要点回顾)

  1. 核心功能std::call_once 结合 std::once_flag 保证多线程下函数仅执行一次,异常时可重试;
  2. 核心优势:比手动加锁更安全(避免DCLP坑)、更高效(执行后无锁开销)、异常友好;
  3. 关键规则
    • 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()):

    cpp 复制代码
    void 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. 避免异步任务捕获局部变量的引用

  • 异步任务若捕获主线程局部变量的引用,可能导致"悬垂引用"(主线程变量销毁后,任务仍访问):

    cpp 复制代码
    void 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,否则析构崩溃
灵活性 支持延迟执行/线程池 仅能创建新线程
适用场景 简单异步任务,需获取结果 复杂线程控制(如线程分离、优先级)

四、典型应用场景

  1. 后台执行耗时任务:如文件读写、网络请求、数据计算,主线程无需阻塞,按需获取结果;
  2. 并行计算 :拆分任务为多个异步子任务,通过 future 获取各子任务结果后汇总;
  3. 延迟执行轻量任务 :用 std::launch::deferred 实现"懒加载",仅在需要时执行任务;
  4. 异常安全的异步操作 :通过 try-catch 捕获异步任务的异常,避免程序崩溃。

总结(核心要点回顾)

  1. 核心功能
    • std::async 启动异步任务,返回 std::future 用于获取结果;
    • std::future 是"未来对象",通过 get() 阻塞获取结果(或 wait() 仅等待);
  2. 关键策略
    • std::launch::async:强制新线程异步执行;
    • std::launch::deferred:延迟执行,get() 时同步执行;
  3. 核心坑点
    • future 局部析构会阻塞等待任务完成;
    • get() 只能调用一次,共享结果用 std::shared_future
    • 默认策略可能导致同步执行,需显式指定异步策略。
相关推荐
枫叶丹42 小时前
【HarmonyOS 6.0】使用PAC脚本灵活管理网络连接
开发语言·网络安全·华为·信息与通信·harmonyos
雾隐潇湘2 小时前
第三章 流程控制语句
开发语言·python
hnlgzb2 小时前
kotlin安卓app中,当一个类继承ViewModel类的时候,这个类是想干什么?
android·开发语言·kotlin
Mr Aokey2 小时前
快速入门 Spring Boot 拦截器、统一响应格式和全局异常处理
java·开发语言·aop·拦截器
瓦哥架构实战2 小时前
CentOS 7 编译安装 Python 3.9 解决 SSL 模块缺失问题
开发语言·python
赵民勇2 小时前
gtkmm库之动作系统详解
linux·c++
宵时待雨2 小时前
C++笔记归纳13:map & set
开发语言·数据结构·c++·笔记·算法
xiangpanf2 小时前
PHP与Vue:前后端技术深度对比
开发语言·vue.js·php
1104.北光c°3 小时前
滑动窗口HotKey探测机制:让你的缓存TTL更智能
java·开发语言·笔记·程序人生·算法·滑动窗口·hotkey