基于任务的编程优于基于线程的编程
Source: Dev.to
更倾向于基于任务的编程而非基于线程的
如果你想异步运行某些工作,有两种基本选择:std::thread 和 std::async。
int doAsyncWork();
std::thread th(doAsyncWork); // 基于线程使用 std::async(推荐)
auto future = std::async(doAsyncWork);std::async(doAsyncWork) 会创建一个 任务。
任务更优,因为它返回一个 std::future,可以用来同步程序。future 提供 get() 方法来获取结果或重新抛出工作函数抛出的任何异常。任务还提供了更高层次的抽象。
什么是线程?
- 硬件线程 – CPU 提供的物理执行单元。现代 CPU 每个核心可能暴露一个或多个硬件线程。
- 软件线程(操作系统或系统线程) – 由操作系统管理的抽象,调度到硬件线程上运行。
std::thread对象是底层软件线程的句柄。
软件线程是一种有限资源。创建的线程数超过系统能够提供的数量会抛出 std::system_error(即使在 noexcept 函数中也是如此)。
即使没有耗尽线程池,过度订阅 也可能发生:就绪运行的软件线程数量多于可用的硬件线程,导致频繁的上下文切换和额外的开销。
为什么更倾向于 std::async?
std::async 将线程创建和调度的管理交给实现。使用 std::async 创建的任务通常可以避免过度订阅问题,因为它 不保证 会创建新的软件线程。相反,调度器可以在需要结果时在已有线程上运行该函数。
当使用默认的启动策略时,实现可以在异步执行(新线程)和延迟执行(同一线程)之间进行选择。
启动策略
| 策略 | 行为 |
|---|---|
std::launch::async | 保证函数在新线程上运行。 |
std::launch::deferred | 函数 不会 立即运行。只有在调用 future.get() 或 future.wait() 时才执行,且在调用线程上运行。 |
默认 (std::launch::async | std::launch::deferred) | 实现自行决定是异步运行任务(新线程)还是延迟运行(同一线程,惰性执行)。 |
使用带有适当启动策略的 std::async,可以编写更清晰、更安全的异步代码,而无需手动处理线程生命周期和过度订阅问题。