點(diǎn)擊上方“C語(yǔ)言與CPP編程”,選擇“關(guān)注/置頂/星標(biāo)公眾號(hào)”
干貨福利,第一時(shí)間送達(dá)!
最近有小伙伴說(shuō)沒(méi)有收到當(dāng)天的文章推送,這是因?yàn)槲⑿鸥牧送扑蜋C(jī)制,導(dǎo)致沒(méi)有星標(biāo)公眾號(hào)的小伙伴刷不到當(dāng)天推送的文章,無(wú)法接收到一些比較實(shí)用的知識(shí)和資訊。所以建議大家加個(gè)星標(biāo)??,以后就能第一時(shí)間收到推送了。
之前做性能優(yōu)化時(shí),我踩了一個(gè)很隱蔽的坑。當(dāng)時(shí)想把一組計(jì)算任務(wù)拆成并行執(zhí)行,順手寫了這么一段代碼:
auto f1 = std::async([]() { return heavy_compute(1); });
auto f2 = std::async([]() { return heavy_compute(2); });
auto r1 = f1.get();
auto r2 = f2.get();
心想著 std::async 內(nèi)部肯定會(huì)起新線程,兩個(gè)任務(wù)并行跑,怎么也能快一倍吧?結(jié)果一上 benchmark,耗時(shí)跟串行幾乎一樣。更離譜的是,在另一臺(tái)機(jī)器上測(cè),它又變快了。
我當(dāng)時(shí)還懷疑是 CPU 調(diào)度問(wèn)題,查了半天才發(fā)現(xiàn):?jiǎn)栴}根本不在機(jī)器,而在 std::async 的默認(rèn)行為里藏了一個(gè)巨大的不確定性。
std::async 有兩個(gè)啟動(dòng)策略:
std::launch::async:強(qiáng)制在新線程異步執(zhí)行std::launch::deferred:延遲執(zhí)行,直到調(diào)用 get() 或 wait() 時(shí)才在當(dāng)前線程執(zhí)行而標(biāo)準(zhǔn)庫(kù)給 std::async 的默認(rèn)策略,是把兩者按位或起來(lái):
// 默認(rèn)等價(jià)于
std::launch::async | std::launch::deferred
這意味著什么?意味著編譯器和運(yùn)行時(shí)有權(quán)決定你的任務(wù)是開(kāi)新線程跑,還是干脆不開(kāi)了,直接在調(diào)用 get() 的線程里串行執(zhí)行。這不是 bug,這是標(biāo)準(zhǔn)允許的實(shí)現(xiàn)自由度。
所以前面的代碼在某些編譯器/平臺(tái)下可能真的開(kāi)了線程,在另一些環(huán)境下就變成了「?jìng)尾⑿小埂D銣y(cè)出來(lái)的性能數(shù)據(jù),本質(zhì)上對(duì)比的是兩種不同策略,而不是并行與串行的差距。
看這段代碼,它用 std::this_thread::get_id() 打印執(zhí)行線程 ID:
#include <iostream>
#include <future>
#include <thread>
int main() {
auto task = []() {
std::cout << "run on thread: " << std::this_thread::get_id() << "\n";
return 42;
};
std::cout << "main thread: " << std::this_thread::get_id() << "\n";
auto f = std::async(task); // 默認(rèn)策略
f.get();
auto f2 = std::async(std::launch::async, task); // 強(qiáng)制異步
f2.get();
return 0;
}
在 MSVC 或某些版本的 libstdc++ 上,你可能會(huì)看到:第一次任務(wù)打印的線程 ID 和主線程完全一樣,而第二次才是不同的線程 ID。這就是 deferred 策略在發(fā)揮作用。
更坑的是,deferred 的任務(wù)在調(diào)用 get() 時(shí)才會(huì)真正執(zhí)行。如果你一口氣提交了三個(gè) std::async,然后依次 get(),結(jié)果就變成了串行執(zhí)行三部曲,跟你直接寫三行順序代碼幾乎沒(méi)有區(qū)別。

除了性能幻覺(jué),deferred 還會(huì)帶來(lái)一些難以排查的副作用:
thread_local | |
wait_for()deferred 狀態(tài),不會(huì)真正等待超時(shí) | |
面試?yán)锶绻粏?wèn)到 "std::async 一定開(kāi)線程嗎?",答"一定"就直接踩雷了。標(biāo)準(zhǔn)從來(lái)沒(méi)保證過(guò)這件事。
如果你確實(shí)需要異步執(zhí)行,不要依賴默認(rèn)策略。顯式寫明意圖:
// 明確要新線程執(zhí)行
auto f = std::async(std::launch::async, []() {
return heavy_compute();
});
// 明確要延遲執(zhí)行(雖然很少這么寫)
auto f2 = std::async(std::launch::deferred, []() {
return lazy_eval();
});
在工程代碼里,我的建議是:把 std::async 的默認(rèn)策略當(dāng)成"不可用"。團(tuán)隊(duì)內(nèi)部可以封裝一個(gè)明確策略的 helper:
template<typename F, typename... Args>
auto async_launch(F&& f, Args&&... args) {
return std::async(std::launch::async,
std::forward<F>(f),
std::forward<Args>(args)...);
}
這樣團(tuán)隊(duì)里沒(méi)人會(huì)再不小心踩到默認(rèn)策略的坑。
std::async 設(shè)計(jì)之初是想簡(jiǎn)化異步編程,但默認(rèn)策略的"自由選擇"反而成了最大的不確定性來(lái)源。它可能創(chuàng)建線程,也可能不創(chuàng)建——標(biāo)準(zhǔn)把決定權(quán)交給了實(shí)現(xiàn),而你只能被動(dòng)接受。
記住三個(gè)要點(diǎn):
std::async 默認(rèn)策略是 async | deferred,執(zhí)行方式不確定std::launch::asyncdeferred 任務(wù)在 get() 時(shí)才執(zhí)行,可能引入串行、死鎖和 thread_local 錯(cuò)亂這個(gè)坑我踩過(guò)一次之后,團(tuán)隊(duì)代碼審查里就多了一條硬性規(guī)定:所有 std::async 必須帶顯式 launch policy。看似多寫了幾個(gè)字符,實(shí)則省下了無(wú)數(shù)個(gè)深夜調(diào)試的 hours。
C++ 校招 / 社招跳槽逆襲!從0到1打造高含金量項(xiàng)目,導(dǎo)師1v1輔導(dǎo),助你斬獲大廠offer!
很多同學(xué)準(zhǔn)備校招時(shí)最焦慮的問(wèn)題就是:“簡(jiǎn)歷沒(méi)項(xiàng)目,怎么打動(dòng)面試官?”
為了解決這個(gè)痛點(diǎn),我們推出了C++訓(xùn)練營(yíng),來(lái)了!

在這里,你可以:
我們不只是教你寫代碼,更帶你走一遍完整的項(xiàng)目流程: 從需求分析、架構(gòu)設(shè)計(jì)、編譯調(diào)試,到版本管理、測(cè)試發(fā)布,全流程掌握!
項(xiàng)目配套資料齊全,遇到問(wèn)題還有導(dǎo)師幫你答疑,不怕卡殼!
項(xiàng)目準(zhǔn)備好了,你只差一次出發(fā)。
相信我,這些項(xiàng)目絕對(duì)能夠讓你進(jìn)步巨大!下面是其中某三個(gè)項(xiàng)目的說(shuō)明文檔

訓(xùn)練營(yíng)適用人群:
不適合人群:

1、C++訓(xùn)練營(yíng),來(lái)了!
2、HarmonyOS 學(xué)習(xí)資料分享(無(wú)套路免費(fèi)分享)
我組建了一些社群一起交流,群里有大牛也有小白,如果你有意可以一起進(jìn)群交流。
歡迎你添加我的微信,我拉你進(jìn)技術(shù)交流群。此外,我也會(huì)經(jīng)常在微信上分享一些計(jì)算機(jī)學(xué)習(xí)經(jīng)驗(yàn)以及工作體驗(yàn),還有一些內(nèi)推機(jī)會(huì)。
加個(gè)微信,打開(kāi)另一扇窗
感謝你的分享,點(diǎn)贊,在看三連