各位同仁各位编程爱好者大家好今天我们齐聚一堂探讨C并发编程中一对核心且强大的工具std::future与std::promise。它们是C11引入的异步编程基石旨在解决跨线程数据传递和结果等待的复杂性。然而正如我们常说的“承诺”容易“兑现”却不易。我们的主题是“我答应你明天给结果结果你等到了后年” 这句话形象地描绘了异步操作中可能遇到的挑战预期的快速响应却可能因为各种原因演变成漫长的等待。我们将深入剖析std::future和std::promise的机制、用法、以及它们在实际应用中可能带来的“陷阱”和最佳实践。目标是让大家不仅理解它们如何工作更重要的是如何避免掉入“无限等待”的泥潭编写出高效、健壮的并发代码。1. 异步编程的基石为什么需要std::future和std::promise在现代多核处理器架构下并发编程已成为提升程序性能和响应能力的关键。当我们在一个线程中启动一个耗时操作例如文件I/O、网络请求、复杂计算时我们不希望主线程或者说发起操作的线程一直阻塞在那里等待结果。相反我们希望它能继续处理其他任务并在耗时操作完成后能够方便地获取其结果。这就是异步编程的精髓发起一个操作但不立即等待其完成而是稍后再回来检查或获取结果。在C中实现这种模式面临几个挑战结果传递如何将子线程的计算结果安全地传递回主线程简单的全局变量或成员变量需要复杂的同步机制如互斥锁、条件变量容易出错。异常处理如果子线程在执行过程中抛出异常如何将其传播到主线程让主线程能够捕获并处理等待机制主线程如何知道子线程何时完成是忙等不断检查还是休眠等待高效利用CPUstd::future和std::promise正是C标准库为解决这些问题而设计的一对协同工作的组件。它们提供了一种清晰、类型安全且异常安全的机制用于在不同的执行上下文通常是不同的线程之间传递一次性结果。std::promise可以看作是“承诺方”或者“结果生产者”。它承诺在未来某个时间点提供一个值或一个异常。std::future则是“消费者”或者“结果接收方”。它代表了未来某个时刻可用的结果。它们通过一个共享状态shared state进行通信。std::promise负责将结果或异常写入这个共享状态而std::future负责从这个共享状态中读取结果或异常。这种机制确保了结果的单次传递和异常的正确传播。2. 深入剖析std::promise结果的许诺者std::promise是一个模板类它持有一个值这个值在未来某个时间点会被设置然后通过其关联的std::future对象来获取。它扮演着异步操作中“结果写入”的角色。2.1std::promise的基本构造与生命周期创建std::promise对象时需要指定它将存储的数据类型。例如std::promiseint表示它将承诺提供一个int类型的结果。#include future #include thread #include iostream #include chrono // For std::chrono::seconds // 辅助函数模拟耗时操作 void sleep_for_ms(int ms) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); } // 异步任务生产者函数 void task_producer(std::promiseint p) { try { std::cout [Producer] 正在努力工作... std::endl; sleep_for_ms(2000); // 模拟2秒耗时操作 int result 42; std::cout [Producer] 设置结果: result std::endl; p.set_value(result); // 兑现承诺设置结果 } catch (...) { // 如果在设置结果前发生异常捕获并设置异常 p.set_exception(std::current_exception()); } } int main_promise_basic() { std::cout --- std::promise 与 std::future 基本用法 --- std::endl; std::promiseint p; // 创建一个promise对象 std::futureint f p.get_future(); // 获取与promise关联的future std::thread t(task_producer, std::move(p)); // 将promise移动到新线程 std::cout [Main] 等待结果... std::endl; int value f.get(); // 阻塞等待结果 std::cout [Main] 收到结果: value std::endl; t.join(); // 等待子线程结束 std::cout ----------------------------------------------- std::endl std::endl; return 0; }代码分析:std::promiseint p;: 创建一个承诺提供int类型结果的promise对象。std::futureint f p.get_future();: 通过p.get_future()获取与此promise关联的future对象。一个promise只能关联一个future并且只能调用一次get_future()。std::thread t(task_producer, std::move(p));: 将promise对象p移动到新创建的线程t中。注意这里使用了std::move(p)因为std::promise是不可复制的但可移动。这意味着promise的所有权被转移到了task_producer函数所在的线程。p.set_value(result);: 在task_producer线程中当计算完成后调用set_value()方法来设置结果。一旦set_value()被调用共享状态就变为“就绪”状态。f.get();: 在主线程中f.get()方法会阻塞当前线程直到共享状态变为就绪即promise设置了值或异常。一旦结果可用get()会返回该值。2.2 兑现承诺set_value()与set_exception()std::promise必须且只能调用一次set_value()或set_exception()来兑现其承诺。一旦兑现其关联的std::future就可以获取结果。void set_value(const T value);/void set_value(T value);: 设置一个成功的值。void set_exception(std::exception_ptr p);: 设置一个异常。这允许异步操作中的异常在等待其结果的线程中被重新抛出。异常传播示例#include future #include thread #include iostream #include stdexcept // For std::runtime_error #include exception // For std::current_exception // 异步任务生产者函数可能抛出异常 void task_producer_with_exception(std::promiseint p, bool throw_error) { try { std::cout [Producer-Exception] 正在努力工作... std::endl; sleep_for_ms(1000); if (throw_error) { std::cout [Producer-Exception] 抛出异常. std::endl; throw std::runtime_error(异步任务中出错了!); } int result 100; std::cout [Producer-Exception] 设置结果: result std::endl; p.set_value(result); } catch (...) { // 捕获任何异常并将其存储到promise中 std::cout [Producer-Exception] 捕获到异常将异常设置到promise中. std::endl; p.set_exception(std::current_exception()); } } int main_promise_exception() { std::cout --- std::promise 的异常传播 --- std::endl; // 场景 1: 无异常 std::promiseint p1; std::futureint f1 p1.get_future(); std::thread t1(task_producer_with_exception, std::move(p1), false); try { std::cout [Main] 等待结果 (预期无异常)... std::endl; int value f1.get(); std::cout [Main] 收到结果: value std::endl; } catch (const std::exception e) { std::cerr [Main] 捕获到意外异常: e.what() std::endl; } t1.join(); std::cout std::endl; // 场景 2: 有异常 std::promiseint p2; std::futureint f2 p2.get_future(); std::thread t2(task_producer_with_exception, std::move(p2), true); try { std::cout [Main] 等待结果 (预期有异常)... std::endl; int value f2.get(); // 这里会重新抛出异常 std::cout [Main] 收到结果: value std::endl; // 不会执行到这里 } catch (const std::runtime_error e) { std::cerr [Main] 成功捕获到预期异常: e.what() std::endl; } catch (const std::exception e) { std::cerr [Main] 捕获到通用异常: e.what() std::endl; } t2.join(); std::cout ----------------------------------------------- std::endl std::endl; return 0; }代码分析:std::current_exception()用于捕获当前线程的异常并返回一个std::exception_ptr对象。p.set_exception(std::current_exception());将这个异常指针存储到promise的共享状态中。当主线程调用f2.get()时它检测到共享状态中存储的是一个异常便会在当前线程重新抛出该异常从而实现了异常的跨线程传播。2.3std::promise的“承诺失效”broken_promise如果一个std::promise对象在它被销毁之前既没有调用set_value()也没有调用set_exception()那么它的承诺就被认为是“失效”了broken promise。此时任何尝试通过其关联的std::future调用get()的线程都会抛出std::future_error异常其错误码是std::errc::broken_promise。这是一个重要的错误处理机制它确保了即使生产者未能明确提供结果消费者也能得到一个明确的失败信号而不是无限期地等待。#include future #include thread #include iostream #include stdexcept // For std::future_error // 承诺未被兑现的生产者函数 void producer_with_broken_promise(std::promiseint p_param) { std::cout [Producer-Broken] 收到承诺但我会忘记兑现它. std::endl; // p_param 在这里超出作用域其析构函数被调用但 set_value 或 set_exception 从未被调用。 // 这将导致 broken_promise。 } int main_broken_promise() { std::cout --- std::promise 的 Broken Promise 示例 --- std::endl; std::promiseint p; std::futureint f p.get_future(); std::thread t(producer_with_broken_promise, std::move(p)); try { std::cout [Main] 等待一个可能失效的承诺的结果... std::endl; int value f.get(); // 这将抛出 std::future_error std::cout [Main] 收到结果: value std::endl; // 这行代码不会被执行 } catch (const std::future_error e) { std::cerr [Main] 捕获到预期的 future_error: e.what() std::endl; if (e.code() std::future_errc::broken_promise) { std::cerr [Main] 错误码指示一个 broken promise. std::endl; } } catch (const std::exception e) { std::cerr [Main] 捕获到通用异常: e.what() std::endl; } t.join(); std::cout ---------------------------------------- std::endl std::endl; return 0; }代码分析:在producer_with_broken_promise函数中p_param对象在函数结束时被销毁但它从未调用set_value()或set_exception()。当主线程调用f.get()时它会检查共享状态发现没有结果也没有异常被设置并且promise已经失效因此抛出std::future_error异常错误码为broken_promise。3. 深入剖析std::future结果的观察者std::future是一个模板类它提供了一种访问异步操作结果的方式。它代表了在未来某个时间点可用的结果。std::future扮演着异步操作中“结果读取”的角色。3.1std::future的获取与结果检索std::future对象通常通过以下两种方式获取从std::promise::get_future()获取。从std::async()函数的返回值获取我们稍后会详细讨论std::async。获取到std::future对象后最核心的操作就是获取结果T get();: 阻塞当前线程直到异步操作完成并设置了结果或异常。一旦结果可用get()返回该值。注意get()只能调用一次。再次调用会导致未定义行为。3.2 避免“无限等待”wait()、wait_for()和wait_until()f.get()的阻塞特性非常强大但也可能导致“你等到了后年”的问题如果异步任务长时间不完成调用get()的线程就会一直卡在那里。为了避免这种情况std::future提供了非阻塞或带超时等待的方法std::future_status wait_for(const std::chrono::duration rel_time);: 等待指定的时间段。如果在这段时间内结果可用它会立即返回。否则在时间段结束后返回。std::future_status wait_until(const std::chrono::time_point abs