
1. 异步编程的“三驾马车”从混乱到秩序在C里写异步代码尤其是涉及到多线程协作的时候很多朋友都经历过一段“黑暗时期”。回调函数套回调函数线程间数据同步搞得人头大一个不小心就是数据竞争或者死锁。我自己也踩过不少坑直到后来系统地用起了std::async、std::packaged_task和std::future/std::promise这套组合拳才感觉从泥潭里爬了出来。今天就来聊聊这三个家伙它们不是什么高深莫测的黑魔法而是C11/14标准库给我们提供的、用来管理异步任务和结果的“三驾马车”目标就一个让异步代码写起来像同步代码一样清晰可控。简单来说你可以把它们理解为一个异步任务生命周期的不同管理者。std::promise像个“承诺者”它负责在一个地方比如某个线程产生一个结果。std::future则是这个承诺的“未来凭证”你可以在另一个地方比如主线程拿着这个凭证去获取结果如果结果还没好它可以等着。std::packaged_task是个“任务打包器”它把一个可调用对象比如函数、lambda和它的promise打包在一起方便你丢给线程去执行。而std::async是更上层的“异步启动器”你给它一个函数和参数它可能注意是可能在后台开个线程帮你执行并直接返回一个future给你取结果。这套机制的核心价值在于它把“任务的执行”和“结果的传递”解耦了。你不用再自己去搞线程创建、锁、条件变量那些底层且易错的东西而是站在一个更高的抽象层次上思考“我要异步做什么”以及“我什么时候要结果”。无论是计算密集型任务、IO等待还是需要并行处理多个独立子任务这套工具都能帮你构建出更安全、更易读的代码结构。接下来我们就一个个拆开看它们到底怎么用以及在实际项目中如何选择。2. 核心组件深度解析与设计哲学2.1 std::promise 与 std::future异步结果的契约这一对是基石理解它们的关系至关重要。std::promise和std::future共同定义了一个单向的、一次性的值传递通道。想象一下promise是生产方它“承诺”在未来某个时间点会提供一个值或异常。future是消费方它持有获取这个“未来值”的权利。std::promise的核心操作设置值 (set_value): 这是履行承诺。一旦调用与之关联的future就会就绪并可以获取到这个值。一个promise只能set_value一次多次设置会抛出std::future_error异常。设置异常 (set_exception): 如果异步任务中发生了异常你可以通过set_exception将异常指针std::exception_ptr存储到promise中。这样在future端调用get()时这个异常会被重新抛出实现了跨线程的异常传递。这是手动处理异步异常非常强大的机制。获取关联的 future (get_future): 每个promise对象都有一个与之配对的future对象。通过get_future()方法获得。这个方法也只能调用一次重复调用同样会抛出异常。std::future的核心操作获取结果 (get): 这是最常用的函数。它会阻塞当前线程直到共享状态就绪即promise设置了值或异常然后返回存储的值或抛出存储的异常。get()只能调用一次调用后future的状态变为无效因为值已经被移走了对于非引用类型。等待 (wait): 阻塞直到结果就绪但不取出结果。可以用于同步点。限时等待 (wait_for,wait_until): 在指定时间段内等待结果就绪。它们返回一个std::future_status枚举表示是就绪(ready)、超时(timeout)还是延迟(deferred与async的策略有关)。检查状态 (valid): 检查这个future对象是否关联着一个有效的共享状态。刚构造的默认future、调用过get()之后的future其valid()都会返回false。设计哲学与注意事项独占所有权:future对象通常不能复制只能移动(move)。这强调了结果的唯一消费者语义。你可以把future移动到另一个上下文如另一个函数或容器去处理结果。一次消费:get()的“一次性”特性强迫开发者思考结果的消费时机和方式避免了意外地多次获取可能已失效的数据。异常安全通道: 通过set_exception/get()标准库为我们搭建了一个安全的跨线程异常传递管道。在传统基于回调或裸线程的编程中处理子线程异常非常棘手常常导致程序静默崩溃。而promise/future机制确保了异常不会被吞没能正确传递到需要处理它的上下文。注意std::promise和std::future本身并不创建线程。它们只是提供了一种同步和传递结果的机制。线程的创建和管理需要其他方式如std::thread或std::async。2.2 std::packaged_task可调用对象的“任务化”包装如果说promise/future提供了结果的通道那么std::packaged_task就是将一个具体的计算任务与这个通道便捷连接起来的适配器。它的模板参数是一个函数签名如int(int, int)构造时需要传入一个符合该签名的可调用对象函数、函数对象、lambda表达式等。它的工作原理是在内部packaged_task持有一个可调用对象和一个std::promise。当你调用packaged_task的operator()执行任务时它实际上会调用内部存储的可调用对象。调用完成后它将结果或异常自动设置到内部的promise中。你可以通过get_future()方法提前获取与这个内部promise关联的future用于之后获取计算结果。它的典型使用场景是线程池或任务队列#include iostream #include future #include thread #include deque int compute_something_heavy(int x, int y) { std::this_thread::sleep_for(std::chrono::seconds(1)); return x * y 100; } int main() { // 1. 创建一个 packaged_task包装我们的计算函数 std::packaged_taskint(int, int) task(compute_something_heavy); // 2. 获取与任务关联的 future std::futureint result_future task.get_future(); // 3. 将任务移动到另一个线程中执行 // 注意packaged_task 不可复制只能移动。移动后原 task 失效。 std::thread worker_thread(std::move(task), 5, 6); // 4. 在主线程做其他事情... std::cout Main thread is doing other work...\n; // 5. 当需要结果时通过 future 获取会阻塞直到计算完成 int result result_future.get(); std::cout The computed result is: result std::endl; worker_thread.join(); return 0; }为什么用packaged_task而不是直接用promise更简洁你不需要手动在任务函数里写promise.set_value(...)packaged_task帮你自动完成了结果传递的绑定。更安全它保证了任务函数的返回值或异常一定会被传递到future减少了手动管理promise时可能出现的遗漏设置值的错误。更适合任务抽象packaged_task本身就是一个可调用对象类型明确由函数签名决定非常适合作为标准化的“任务单元”被放入std::function、队列或线程池中管理。实操心得当你要把一个现有的函数丢到线程里跑并且想方便地拿到结果时packaged_task是你的首选。尤其是在实现一个简单的线程池时你可以定义一个using Task std::packaged_taskvoid();然后将各种任务包装成Task对象塞进任务队列工作线程从队列取Task执行即可。消费者可以通过Task对应的future来等待任务完成或获取返回值。2.3 std::async更上层的异步策略抽象std::async是一个函数模板它试图提供一种“最省心”的异步执行方式。你告诉它“帮我异步执行这个函数”它返回一个future。至于它到底是怎么执行的——是在新线程、线程池还是就在当前线程延迟执行——这取决于你传递给它的启动策略以及库的实现。两种启动策略std::launch::async要求函数必须在新线程中异步执行。std::launch::deferred要求函数延迟执行。即只在返回的future上调用get()或wait()时才在当前线程中同步执行函数。你也可以不指定策略使用默认参数std::launch::async | std::launch::deferred。这意味着实现可以自由选择是立即异步执行还是延迟执行。这是不指定策略时的默认行为也是需要注意的地方。#include iostream #include future #include chrono int find_the_answer() { std::this_thread::sleep_for(std::chrono::seconds(2)); return 42; } void do_other_stuff() { for(int i 0; i 3; i) { std::cout Doing other stuff... i std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(500)); } } int main() { // 方式1明确指定异步执行 std::futureint answer_async std::async(std::launch::async, find_the_answer); do_other_stuff(); std::cout The answer (async) is: answer_async.get() std::endl; // 方式2使用延迟执行 std::futureint answer_deferred std::async(std::launch::deferred, find_the_answer); // 此时 find_the_answer 函数还没有被调用 do_other_stuff(); std::cout Now getting the deferred answer...\n; // 调用 get() 时find_the_answer 在当前线程主线程同步执行 std::cout The answer (deferred) is: answer_deferred.get() std::endl; // 方式3使用默认策略不推荐在需要明确并发时使用 std::futureint answer_default std::async(find_the_answer); // 实现可能选择 async 或 deferred行为不确定 // 如果实现选择了 deferred并且你忘了调用 get()那么任务根本不会执行 return 0; }std::async的优势与陷阱优势极其简洁一行代码就能启动异步任务并拿到future无需手动创建thread、packaged_task或promise。自动资源管理返回的future在析构时如果启动策略是async它会等待关联的异步任务完成即隐式执行了wait。这避免了“忘记join线程”导致的资源泄露或未定义行为。这个特性被称为“等待析构”。陷阱与注意事项默认策略的歧义性最大的坑就是默认启动策略。如果你写auto fut std::async(func);那么func可能被延迟执行。这意味着如果你后续没有对fut调用wait或getfunc可能永远不会执行。即使你调用了get它也是在调用者的线程中同步执行的失去了并发的意义。最佳实践如果希望任务真正并发总是明确指定std::launch::async策略。“等待析构”可能引入意外阻塞虽然这个特性防止了资源泄露但也可能带来问题。考虑以下场景void fire_and_forget() { // 启动一个异步任务但不保存返回的 future std::async(std::launch::async, []{ std::this_thread::sleep_for(std::chrono::seconds(10)); std::cout Long task done.\n; }); // 临时 future 在此析构由于它是 async 策略析构会等待任务完成 // 所以 fire_and_forget 函数会在这里阻塞10秒这完全违背了“发射后不管”的初衷。 }解决方法如果你真的想实现“发射后不管”要么使用std::thread并 detach不推荐失去控制要么将返回的future存储到某个生命周期更长的对象中例如全局容器、类成员或者使用更底层的线程库来管理。线程资源开销每次调用std::async(std::launch::async, ...)实现都可能创建一个新的线程。对于大量短小的任务频繁创建销毁线程的开销很大。在这种情况下使用线程池配合packaged_task通常是更高效的选择。3. 实战中的组合应用与模式选择理解了单个组件的用法我们来看看在实际项目中如何把它们组合起来解决具体问题。选择哪种工具取决于你对任务控制粒度的需求。3.1 场景一并行计算与结果聚合这是最经典的场景。比如你需要并行处理一批数据然后汇总结果。#include vector #include future #include numeric #include iostream #include chrono // 一个模拟的耗时计算任务 int process_chunk(const std::vectorint data, int start, int end) { int sum 0; for (int i start; i end; i) { sum data[i]; // 模拟计算 std::this_thread::sleep_for(std::chrono::microseconds(10)); // 模拟耗时 } return sum; } int main() { const int data_size 10000; const int num_threads 4; const int chunk_size data_size / num_threads; std::vectorint big_data(data_size); std::iota(big_data.begin(), big_data.end(), 1); // 填充1,2,3,...10000 std::vectorstd::futureint futures; futures.reserve(num_threads); auto start_time std::chrono::high_resolution_clock::now(); // 使用 async 启动多个并行任务 for (int i 0; i num_threads; i) { int chunk_start i * chunk_size; int chunk_end (i num_threads - 1) ? data_size : chunk_start chunk_size; // 明确使用 async 策略确保并发 futures.emplace_back( std::async(std::launch::async, process_chunk, std::cref(big_data), chunk_start, chunk_end) ); } // 收集并汇总结果 int total_sum 0; for (auto fut : futures) { total_sum fut.get(); // 按顺序或任意顺序 get都会阻塞直到对应任务完成 } auto end_time std::chrono::high_resolution_clock::now(); auto duration std::chrono::duration_caststd::chrono::milliseconds(end_time - start_time); std::cout Total sum: total_sum std::endl; std::cout Parallel computation took duration.count() ms. std::endl; // 对比串行时间 start_time std::chrono::high_resolution_clock::now(); int serial_sum process_chunk(big_data, 0, data_size); end_time std::chrono::high_resolution_clock::now(); duration std::chrono::duration_caststd::chrono::milliseconds(end_time - start_time); std::cout Serial computation took duration.count() ms. std::endl; return 0; }模式选择理由在这个场景下std::async是最佳选择。因为任务划分清晰彼此独立我们只关心最终结果不关心中间调度细节。async的简洁性得以充分发挥。注意我们使用了std::launch::async来确保真正的并发。3.2 场景二构建简易线程池或任务队列当你需要控制并发线程的数量或者任务产生和消费速率不一致时就需要一个任务队列。这时std::packaged_task就派上用场了。#include iostream #include future #include thread #include queue #include mutex #include condition_variable #include functional #include vector class SimpleThreadPool { public: using Task std::packaged_taskvoid(); // 定义任务类型无返回值 explicit SimpleThreadPool(size_t num_threads) { workers_.reserve(num_threads); for (size_t i 0; i num_threads; i) { workers_.emplace_back([this] { this-worker_loop(); }); } } ~SimpleThreadPool() { { std::unique_lockstd::mutex lock(queue_mutex_); stop_ true; } condition_.notify_all(); for (auto worker : workers_) { if (worker.joinable()) worker.join(); } } // 提交一个任务返回一个 future 用于等待任务完成 std::futurevoid submit(Task task) { auto future task.get_future(); { std::unique_lockstd::mutex lock(queue_mutex_); if (stop_) { throw std::runtime_error(submit on stopped ThreadPool); } tasks_.push(std::move(task)); } condition_.notify_one(); return future; } // 便捷函数提交一个任意可调用对象 templatetypename F, typename... Args auto submit(F f, Args... args) - std::futuredecltype(f(args...)) { // 用 packaged_task 包装用户函数并推导返回类型 using return_type decltype(f(args...)); auto task std::packaged_taskreturn_type()( std::bind(std::forwardF(f), std::forwardArgs(args)...) ); auto future task.get_future(); // 将任务转为 void() 类型以匹配我们的任务队列 submit(std::packaged_taskvoid()([task std::move(task)]() mutable { task(); })); return future; } private: void worker_loop() { while (true) { Task task; { std::unique_lockstd::mutex lock(queue_mutex_); condition_.wait(lock, [this] { return stop_ || !tasks_.empty(); }); if (stop_ tasks_.empty()) return; task std::move(tasks_.front()); tasks_.pop(); } task(); // 执行打包的任务 } } std::vectorstd::thread workers_; std::queueTask tasks_; std::mutex queue_mutex_; std::condition_variable condition_; bool stop_ false; }; // 使用示例 int main() { SimpleThreadPool pool(4); std::vectorstd::futureint results; for (int i 0; i 8; i) { // 提交任务到线程池并获取 future auto future pool.submit([i]() - int { std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout Task i executed by thread std::this_thread::get_id() std::endl; return i * i; }); results.push_back(std::move(future)); } // 获取所有任务的结果 for (size_t i 0; i results.size(); i) { std::cout Result of task i : results[i].get() std::endl; } return 0; }模式选择理由线程池的核心是“任务”的抽象。std::packaged_task完美扮演了这个角色。它将用户想要执行的函数和其返回值的“承诺”promise打包在一起形成一个可以移动、可以存储、可以统一执行的Task对象。线程池的工作线程只需要从队列中取出Task并执行它任务的发起者则通过Task对应的future来获取结果。这种模式分离了任务的提交、执行和结果获取是构建复杂并发系统的基础。3.3 场景三复杂的线程间协作与条件触发有些时候异步操作的结果不是简单的返回值而是需要在多个点、由多个线程来设置或等待。或者一个任务需要等待另一个任务产生的某个中间事件。这时直接使用std::promise和std::future会更灵活。#include iostream #include future #include thread #include vector // 模拟一个多阶段处理流水线 void stage1_processor(std::promisestd::vectorint input_promise) { try { std::cout Stage1: Generating data...\n; std::this_thread::sleep_for(std::chrono::seconds(1)); std::vectorint data {1, 2, 3, 4, 5}; // 履行承诺将数据传递给下一阶段 input_promise.set_value(std::move(data)); std::cout Stage1: Data sent.\n; } catch (...) { // 如果发生异常传递给下一阶段 input_promise.set_exception(std::current_exception()); } } void stage2_processor(std::futurestd::vectorint input_future, std::promiseint result_promise) { try { std::cout Stage2: Waiting for data...\n; auto data input_future.get(); // 阻塞等待 stage1 的数据 std::cout Stage2: Processing data...\n; std::this_thread::sleep_for(std::chrono::seconds(1)); int sum 0; for (int val : data) sum val; // 将处理结果传递给最终阶段 result_promise.set_value(sum); std::cout Stage2: Result sent.\n; } catch (...) { result_promise.set_exception(std::current_exception()); } } int main() { // 创建两个 promise-future 对用于阶段间通信 std::promisestd::vectorint stage1_to_stage2_promise; std::futurestd::vectorint stage2_input_future stage1_to_stage2_promise.get_future(); std::promiseint stage2_to_main_promise; std::futureint final_result_future stage2_to_main_promise.get_future(); // 启动 stage1 和 stage2 线程 std::thread t1(stage1_processor, std::move(stage1_to_stage2_promise)); std::thread t2(stage2_processor, std::move(stage2_input_future), std::move(stage2_to_main_promise)); // 主线程等待最终结果 std::cout Main: Waiting for final result...\n; try { int final_result final_result_future.get(); std::cout Main: Final result received: final_result std::endl; } catch (const std::exception e) { std::cerr Main: Exception caught: e.what() std::endl; } t1.join(); t2.join(); return 0; }模式选择理由在这个流水线场景中stage1和stage2之间需要传递一个复杂对象std::vectorint并且stage2必须等待stage1完成。直接使用promise/future对可以清晰地建立这种单向的、一次性的数据流通道。每个promise都是一个明确的数据生产端点每个future都是一个明确的数据消费端点。这种显式的数据流比隐式的全局变量或回调函数更安全、更易于推理。packaged_task在这里反而不太适合因为每个阶段的任务逻辑和传递的数据类型都不同。4. 常见陷阱、性能考量与调试技巧即使理解了原理在实际使用中还是会遇到各种坑。下面是一些我踩过或者见别人踩过的坑以及对应的解决思路。4.1 共享状态的生命周期管理promise、future和shared_future都关联着一个“共享状态”。这个状态在堆上分配由这些对象共享所有权通过引用计数。理解它的生命周期是避免悬空引用和未定义行为的关键。promise析构的影响如果promise在设置值或异常之前就被析构了那么与之关联的future在调用get()时会抛出std::future_error异常错误码为std::future_errc::broken_promise。这通常意味着异步任务侧发生了错误未能履行承诺。std::futureint bad_future; { std::promiseint p; bad_future p.get_future(); // p 离开作用域被析构没有 set_value 或 set_exception } // 此时 bad_future 关联的 promise 已失效 try { int val bad_future.get(); // 抛出 std::future_error (broken_promise) } catch (const std::future_error e) { std::cout Caught: e.what() std::endl; }教训确保promise对象的生命周期至少持续到它履行了承诺设置了值或异常。future析构的影响对于从std::async且启动策略为async返回的future其析构函数会阻塞直到关联的异步任务完成。对于从packaged_task或promise获取的future其析构函数只是释放对共享状态的引用不会阻塞。如果共享状态已就绪则正常释放如果未就绪且这是最后一个引用则共享状态也会被销毁可能导致另一端的promise变成broken_promise。最佳实践总是考虑future的生命周期。如果不想阻塞就不要让从std::async返回的临时future立即析构如前文所述。对于重要的结果确保持有future直到你处理完结果。4.2 std::shared_future结果的多次消费std::future是独占的结果只能取一次。但有时多个线程或代码段需要等待同一个异步事件的结果。这时就需要std::shared_future。它可以被复制多个shared_future对象共享同一个共享状态并且每个都可以调用get()获取结果对于值类型get()返回const T所以不会移动走数据。std::promisevoid start_signal_promise; std::shared_futurevoid start_signal start_signal_promise.get_future().share(); // 转为 shared_future std::vectorstd::thread workers; for (int i 0; i 5; i) { workers.emplace_back([i, start_signal] { // 每个线程复制一份 shared_future std::cout Worker i waiting...\n; start_signal.wait(); // 所有线程等待同一个信号 std::cout Worker i started!\n; }); } std::this_thread::sleep_for(std::chrono::seconds(2)); std::cout Ready, set, GO!\n; start_signal_promise.set_value(); // 触发所有等待的线程 for (auto t : workers) t.join();使用场景实现“闸门”模式所有线程等待一个开始信号或者广播一个计算结果给多个消费者。4.3 性能开销与线程池选择std::async虽然方便但其默认实现如GCC的libstdc、Clang的libc可能为每个任务创建新线程。对于大量成千上万的微小任务线程创建和销毁的开销会成为瓶颈。性能对比建议微小任务 1ms避免使用std::async考虑使用线程池。线程池复用固定数量的线程避免了频繁的线程创建销毁开销。你可以自己实现如前文示例也可以使用第三方库如 Intel TBB、Microsoft PPL。中等及以上任务 10msstd::async的开销相对可以接受其简洁性优势明显。IO密集型任务线程会因为IO而阻塞使用std::async可能创建大量阻塞线程。考虑使用异步IO库如asio或协程C20来获得更高的并发能力。一个简单的基准测试思路auto start std::chrono::high_resolution_clock::now(); std::vectorstd::futurevoid futures; for (int i 0; i 1000; i) { futures.push_back(std::async(std::launch::async, []{ // 一个非常微小的任务比如几次整数加法 volatile int x 0; // volatile 防止被优化掉 for(int j0; j100; j) x j; })); } // 所有 future 析构时会等待任务完成 futures.clear(); auto end std::chrono::high_resolution_clock::now(); // 对比使用线程池执行同样数量任务的时间4.4 调试异步程序死锁与数据竞争异步编程引入了并发经典的并发问题也随之而来。死锁future.get()是阻塞调用。如果你在主线程get()一个由主线程启动的、且策略为deferred的async任务就会死锁任务需要主线程执行但主线程在等待任务完成。同样两个线程互相等待对方promise设置值也会死锁。排查技巧画出示意图理清线程间的数据依赖和等待关系。使用调试器观察线程状态看哪些线程在阻塞wait。数据竞争即使通过future传递结果如果多个线程访问共享的非线程安全数据依然会有数据竞争。std::vectorint shared_data; std::futurevoid fut std::async(std::launch::async, [shared_data]{ shared_data.push_back(42); // 潜在的数据竞争 }); shared_data.push_back(100); // 主线程也在修改 fut.wait();解决方法要么通过future/promise传递数据的副本或移动所有权彻底消除共享要么使用互斥锁std::mutex保护共享数据。记住future只解决了单个结果的传递和同步不解决通用的共享数据访问问题。异常丢失如果异步任务中抛出异常但没有被promise.set_exception捕获或者packaged_task包装的任务抛出异常但无人调用future.get()这个异常可能会被默默忽略导致程序行为异常却无日志。最佳实践在异步任务的顶层用try...catch捕获所有异常并通过promise.set_exception传递出去。确保在某个地方调用future.get()或future.wait()来触发可能的异常抛出。4.5 错误码速查表在使用future和promise时可能会遇到std::future_error异常。了解其错误码有助于快速定位问题。错误码 (std::future_errc)含义常见原因broken_promise承诺被破坏关联的promise在未设置值或异常的情况下被析构。future_already_retrievedfuture 已被获取对同一个promise多次调用get_future()。promise_already_satisfied承诺已被满足对同一个promise多次调用set_value()或set_exception()。no_state无共享状态在一个默认构造的无效的future或promise上调用方法如get(),set_value()。处理这些异常通常意味着你的程序逻辑有 bug需要检查promise和future的生命周期以及设置/获取的调用顺序。5. 迈向现代C协程与异步操作的未来C20 引入了协程Coroutines它为异步编程提供了另一种更强大、更直观的模型。协程允许你以近乎同步的代码风格来编写异步操作底层由编译器帮你转换为状态机。虽然std::async、future、promise在未来很长一段时间内仍会广泛使用但了解协程的基本思想是有益的。简单来说协程是一个可以暂停执行并在之后恢复的函数。结合std::future我们可以设想一种更优雅的异步代码// 伪代码/概念展示并非完全有效的C20代码 std::futureint async_add(int a, int b) { co_return a b; // co_return 表示这是一个协程返回一个 future } std::futurevoid example() { // 以同步方式调用异步函数不阻塞线程 int sum co_await async_add(10, 20); std::cout The sum is: sum std::endl; // 可以顺序执行多个异步操作代码清晰 int another_sum co_await async_add(sum, 30); std::cout Another sum is: another_sum std::endl; }在协程模型中co_await表达式会挂起当前协程等待异步操作完成而线程可以去执行其他任务。当异步操作完成后协程在合适的线程恢复执行。这避免了回调地狱Callback Hell也让基于future的链式调用.then()显得过时。当前C17及之前的建议是熟练掌握async/packaged_task/promise/future这一套工具它们是构建可靠、可维护异步程序的基石。对于新的项目如果编译器支持且团队熟悉可以开始探索 C20 协程与相关的异步库如cppcoro。但务必记住任何新技术都需要评估其成熟度、编译器支持度和团队学习成本。对于大多数现有项目基于future的模型在未来五年内依然会是主流选择。