• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2025-11-13 16:47 Aet 隐藏边栏 |   抢沙发  1 
文章评分 1 次,平均分 5.0

async

概述

  1. 用于简化异步任务执行的高级抽象
  2. 它的核心目标是让你能方便地启动一个任务并在未来获取其结果,而无需直接管理线程的生命周期、同步等底层细节
  3. std::async可以看作是 std::promise, std::packaged_taskstd::thread的高级封装

策略

  1. std::launch::async
    1. 异步执行:立即(或尽快)在新线程中运行
    2. 创建新线程或利用线程池
    3. 需要真正并发、计算密集型或独立的耗时任务
    4. 行为明确,确定为异步
  2. std::launch::deferred
    1. 同步延迟执行:仅在调用 get()wait()时,在调用线程中运行
    2. 不使用新线程,任务在请求结果的线程上运行
    3. 不要求并行,可能永不执行或希望惰性求值的任务
    4. 行为明确,确定为延迟同步

核心机制:共享状态

  1. 概述
    1. std::async的强大功能依赖于一个关键的内部数据结构:共享状态(Shared State
  2. 这个共享状态是一个线程安全的上下文,主要用于存储:
    1. 任务的执行状态:标识任务是否已完成、正在进行中或被延迟
    2. 任务的返回值:任务执行完毕后的结果
    3. 任务抛出的异常:如果任务中抛出了异常,异常对象会被捕获并存储于此

原理

  1. 调用 std::async时,它会进行一系列幕后操作:
    1. 创建一个共享状态
    2. 将你提交的任务(函数、函数对象等)及其参数打包
      通常内部会使用 std::packaged_task进行封装,后者能自动将结果或异常存入共享状态
    3. 根据指定的启动策略,决定如何执行这个打包好的任务
    4. 返回一个 std::future对象给你
  2. std::future对象
    1. 这个 std::future对象就像是通往共享状态的唯一句柄或钥匙
    2. 当你调用 future.get()时,当前线程会阻塞,直到异步任务执行完毕
    3. 然后,get()方法会从共享状态中取出结果(或重新抛出存储的异常)
  3. 需要注意的是,get()方法只能调用一次,因为它会转移或消耗掉共享状态中的值

future

概述

核心机制:共享状态

  1. std::future机制的核心
  2. 它通常包含:
    1. 存储的值或异常:任务的计算结果或执行过程中抛出的异常
    2. 就绪状态标志:指示结果/异常是否已经准备好
    3. 同步原语:如互斥锁(mutex)和条件变量(condition variable),用于管理线程间的等待和通知

线程同步与阻塞

  1. 调用 future.get()时,会发生以下情况
    1. 检查共享状态是否已经就绪
    2. 如果已经就绪,立即返回存储的值或抛出存储的异常
    3. 如果尚未就绪,当前调用线程会被阻塞。内部实现通常会使用条件变量让线程等待
    4. Provider(如 std::promise::set_value)设置了值或异常后,共享状态被标记为就绪,并通知(notify)等待的线程
      此时,被 get()阻塞的线程会被唤醒,继续执行

promise

概述

  1. C++11 中一个强大的线程间通信工具,它和 std::future配合,像一个连接两个线程的单次通信管道,允许数据或异常安全地从“生产者”线程传递到“消费者”线程

组件

  1. std::promise(生产者)
    1. 承诺在未来提供结果的对象
    2. set_value(), set_exception()
  2. std::future(消费者)
    1. 用于获取未来某个时刻的结果的对象
    2. get(), wait()
  3. 共享状态 (通信桥梁)
    1. 内部数据结构,存储结果、异常及同步原语

核心机制:共享状态

  1. std::promisestd::future并不直接对话,它们通过一个名为共享状态(Shared State)的内部数据结构进行通信
  2. 这个共享状态是线程安全的,通常包含:
    1. 存储的值或异常:任务的计算结果或执行过程中抛出的异常
    2. 就绪状态标志:指示结果/异常是否已经准备好
    3. 同步原语:如互斥锁(mutex)和条件变量(condition variable),用于管理线程间的等待和通知

线程同步与阻塞

  1. 当消费者线程调用 future.get()时,会检查共享状态是否就绪:
    1. 如果已经就绪,则立即返回存储的值或抛出存储的异常
    2. 如果尚未就绪,当前调用线程会被阻塞。内部实现通常会使用条件变量让线程等待,直到 promise设置结果并发出通知

异常的安全传递

  1. std::promise提供了一个安全的跨线程异常传递通道
    1. 如果异步任务中抛出了未被捕获的异常,可以使用 set_exception将异常指针(std::exception_ptr)存储到共享状态中
    2. 当消费者线程调用 future.get()时,这个异常会在当前线程中被重新抛出,使得跨线程错误处理与本地异常处理一样自然

注意

  1. 一个 std::promise只能设置一次值或异常。多次调用 set_value会导致未定义行为
    1. 同时,每个 promise只能调用一次 get_future()方法,多次调用会抛出 std::future_error异常
  2. 如果 std::promise对象在析构前都未设置任何值或异常(即承诺未被履行),那么与之关联的 std::future在调用 get()时会抛出 std::future_error异常,其错误码为 std::future_errc::broken_promise
  3. std::promise不可复制,但支持移动语义(移动构造函数和移动赋值操作符)
  4. std::promise提供了 set_value_at_thread_exit函数
    1. set_value立即通知等待线程不同,它会在当前线程退出、所有线程局部存储对象被销毁后,才将共享状态标记为就绪
    2. 这可以避免某些情况下,等待线程因立即被唤醒而访问到尚未完全清理的线程局部资源

packaged_task

概述

  1. C++11 中一个非常实用的异步编程工具,它充当了可调用任务与未来结果之间的“绑定器”

组成

  1. 被包装的任务 (Stored Task)
    1. 存储用户提供的可调用对象(如函数、lambda 表达式),定义了需要异步执行的逻辑
  2. 共享状态 (Shared State)
    1. 一个线程安全的内部数据结构,作为中间媒介,用于存储任务执行后的返回值或抛出的异常
  3. std::future 关联接口
    1. 通过 get_future()方法产生一个与共享状态绑定的 std::future对象,是获取结果的唯一凭证

核心机制

  1. 任务包装与对象创建
    1. 当你创建一个 std::packaged_task对象时,需要指定一个函数签名(例如 int(int, int))并将一个可调用对象(如函数、lambda表达式)传递给它
    2. 此时,packaged_task内部会存储这个任务,并初始化一个空的共享状态

  1. 获取未来凭证
    1. 通过调用 task.get_future()方法,你可以获得一个与该任务共享状态关联的 std::future对象
    2. 一个 packaged_task只能调用一次 get_future()

  1. 执行任务与结果传递
    1. 可以像调用普通函数一样调用 task(),或者在新的线程中执行它(由于 packaged_task不可复制,传递到线程需要使用 std::move
    2. 当任务被调用执行后,其返回值会被自动捕获并存储到共享状态中
    3. 如果任务执行过程中抛出了异常,该异常也会被捕获并存储到共享状态中,而不是立即终止程序
    4. 无论哪种情况,共享状态都会被标记为 就绪
  2. 获取结果
    1. 在需要结果的地方,调用 result_future.get()。这个操作会:
    2. 如果共享状态已就绪,立即返回存储的值或重新抛出存储的异常
    3. 如果共享状态未就绪,则阻塞当前线程,直到任务执行完成

注意

  1. std::packaged_task禁用了拷贝构造函数和拷贝赋值操作,但支持移动语义
  2. 必须确保 std::packaged_task对象最终被执行。如果它在其生命周期内从未被调用(即承诺未履行),那么与之关联的 std::future在调用 get()时会抛出 std::future_error异常,错误码为 broken_promise
  3. 一个 std::packaged_task对象通常只能执行一次
    1. 如果需要再次执行相同逻辑的任务,需要创建新的对象
    2. 或者使用其 reset()方法(该方法会重置共享状态,但保留被包装的任务)

其他

promise对比async普通return返回

  1. 普通返回(return)的时候,一定是线程结束的时候
  2. promise通过set_value返回状态的时候,不需要是线程结束的时机,还可以在set_value之后继续处理其他任务

asyncpackaged_taskpromise总结

  1. async
    1. 封装了thread,自管理生命周期
    2. 两种模式,异步模式或延迟同步模式
    3. 内部会自动捕获返回值或异常,存储到future里面
    4. 通过future获取结果,阻塞(任务没完成)或不阻塞(任务已完成)当前线程
  2. packaged_task
    1. 封装可调用对象
    2. 内置promise,自动捕获返回值或异常后通过set_value把返回值或异常存储到future
    3. 通过future获取结果,阻塞(任务没完成)或不阻塞(任务已完成)当前线程
  3. promise
    1. 随便一个时机都可以调用set_value,不需要像普通返回那样return完线程就结束了
    2. 而且必须要调用set_value
    3. 通过future获取结果,阻塞(另一线程还未调用set_value)或不阻塞(另一线程已调用set_value)当前线程

声明:本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享