Wiki
jthread 與 stop_token
本文由簡體中文內容確定性轉換,並受版本化術語表保護。
C++20 提供了:
std::jthread
它可以理解成“更安全的 std::thread”:
- 析構時自動處理執行緒,不要求每條控制路徑都手寫
join(); - 內建協作式停止機制;
- 可以與
std::stop_token、std::stop_source、std::stop_callback配合。
如果專案使用 C++20,並且執行緒是“啟動後持續執行,退出時需要安全停止”的型別,std::jthread 通常比裸 std::thread 更合適。
1.std::thread 的生命週期風險
使用 std::thread 時:
std::thread t(work);
必須保證物件析構前已經:
t.join();
或者:
t.detach();
否則 joinable 的 std::thread 析構會呼叫:
std::terminate()
這意味著異常、提前 return、複雜分支都可能讓“是否已經 join”變成維護負擔。
2.std::jthread 會自動 join
最簡單的寫法:
#include <iostream>
#include <thread>
void work()
{
std::cout << "working\n";
}
int main()
{
std::jthread t(work);
}
離開作用域時,不需要手動寫:
t.join();
jthread 的解構函式會負責等待執行緒結束。
這讓執行緒管理更符合 RAII 思想。
3.jthread 不等於“強制殺死執行緒”
std::jthread 的停止機制是:
協作式停止(cooperative cancellation)
也就是說:
某个线程发出停止请求
↓
工作线程主动检查这个请求
↓
工作线程自己决定在安全位置退出
標準庫不會粗暴地強制終止執行緒。
這是非常重要的設計,因為強制殺執行緒可能發生在:
- 正持有 mutex;
- 正修改物件狀態;
- 正寫檔案;
- 正執行資源管理程式碼;
如果在任意位置突然中斷,很容易破壞程式狀態。
4.std::stop_token
std::stop_token 表示:
我可以觀察某個停止請求是否已經發出。
jthread 可以自動把 stop token 作為執行緒函式的第一個引數傳入。
例如:
#include <chrono>
#include <iostream>
#include <stop_token>
#include <thread>
using namespace std::chrono_literals;
void worker(std::stop_token stop_token)
{
while (!stop_token.stop_requested())
{
std::cout << "working\n";
std::this_thread::sleep_for(100ms);
}
std::cout << "stopping\n";
}
int main()
{
std::jthread thread(worker);
std::this_thread::sleep_for(350ms);
thread.request_stop();
}
這裡執行緒函式的第一個引數:
std::stop_token
由 jthread 自動提供,不需要呼叫者手動傳。
5.request_stop()
呼叫:
thread.request_stop();
只是“發出停止請求”。
它不會等待執行緒結束,也不會強制終止執行緒。
工作執行緒需要主動檢查:
stop_token.stop_requested()
並自行退出。
6.stop_possible()
stop_token 還可以查詢:
stop_token.stop_possible()
表示這個 token 是否關聯著一個能夠產生停止請求的停止狀態。
普通 jthread 自動提供的 token 通常是可停止的。
7.jthread 析構時會發生什麼
如果一個 jthread 在析構時仍然 joinable,析構過程概念上會:
request_stop()
↓
join()
也就是說,它不只是自動等待,還會先請求執行緒停止。
因此:
{
std::jthread thread(worker);
} // 离开作用域时自动请求停止并等待结束
如果 worker 正確響應 stop token,就能自然退出。
8.析構請求停止不等於一定能立刻退出
如果工作執行緒完全不檢查 stop token:
void worker(std::stop_token)
{
while (true)
{
}
}
那麼 jthread 析構時雖然呼叫了 request_stop(),執行緒依然不會退出。
隨後析構中的 join() 會一直等待。
所以:
jthread提供停止協議,但執行緒函式必須主動合作。
9.阻塞操作也要考慮停止
下面雖然檢查了停止請求:
while (!stop_token.stop_requested())
{
blocking_operation();
}
但如果:
blocking_operation()
一次就能阻塞幾十秒,那麼停止響應仍然會非常慢。
執行緒設計不僅要考慮“有沒有 stop token”,還要考慮:
- 阻塞呼叫是否支援超時;
- 是否能被喚醒;
- 是否可以分階段檢查停止狀態。
10.std::stop_source
std::stop_source 是停止請求的“控制端”。
可以:
std::stop_source source;
std::stop_token token = source.get_token();
然後:
source.request_stop();
所有關聯到這個停止狀態的 token 都能觀察到請求。
示例:
#include <iostream>
#include <stop_token>
int main()
{
std::stop_source source;
std::stop_token token = source.get_token();
std::cout << std::boolalpha
<< token.stop_requested()
<< '\n';
source.request_stop();
std::cout << token.stop_requested() << '\n';
}
輸出:
false
true
11.std::stop_callback
有時不希望某個執行緒一直輪詢:
stop_requested()
而是希望停止請求到來時自動執行一段回撥。
可以使用:
std::stop_callback
例如:
#include <iostream>
#include <stop_token>
int main()
{
std::stop_source source;
std::stop_token token = source.get_token();
std::stop_callback callback(token, [] {
std::cout << "stop requested\n";
});
source.request_stop();
}
request_stop() 發出停止請求時,已註冊的 callback 會執行。
注意 callback 的執行上下文與停止請求有關,因此回撥本身也應該保持簡單,並避免製造新的鎖順序問題。
12.jthread 也支援普通執行緒函式
並不是所有 jthread 函式都必須接收 stop_token。
例如:
#include <iostream>
#include <thread>
void work(int value)
{
std::cout << value << '\n';
}
int main()
{
std::jthread thread(work, 42);
}
如果可呼叫物件能夠以:
(stop_token, args...)
形式呼叫,jthread 會優先傳入 stop token。
否則就像普通 thread 一樣使用:
(args...)
形式呼叫。
13.帶引數的 stop token 執行緒函式
#include <iostream>
#include <stop_token>
#include <thread>
void worker(std::stop_token token, int id)
{
std::cout << "worker " << id << '\n';
while (!token.stop_requested())
{
// work
}
}
int main()
{
std::jthread thread(worker, 7);
thread.request_stop();
}
這裡:
std::jthread thread(worker, 7);
呼叫效果相當於讓執行緒執行:
worker(自动提供的 stop_token, 7)
14.jthread 同樣可以 join
自動 join 不代表不能手動 join。
std::jthread thread(work);
thread.join();
呼叫後:
thread.joinable() == false
之後析構時就無需再次等待。
同樣可以查詢:
thread.joinable()
thread.get_id()
15.jthread 也可以 detach,但通常不推薦
jthread 仍然提供:
thread.detach();
但一旦 detach,RAII 自動等待和結構化停止的優勢就基本失去了。
除非確實明確理解生命週期,否則不要為了“後臺執行”輕易 detach。
16.condition_variable_any 與 stop token
普通 std::condition_variable 沒有直接接收 stop_token 的等待過載。
C++20 的:
std::condition_variable_any
則可以把停止請求整合進等待條件。
例如:
#include <condition_variable>
#include <mutex>
#include <stop_token>
#include <thread>
std::mutex mutex;
std::condition_variable_any cv;
bool ready = false;
void worker(std::stop_token token)
{
std::unique_lock lock(mutex);
bool condition_met = cv.wait(
lock,
token,
[] {
return ready;
});
if (!condition_met)
{
// 因停止请求结束等待
return;
}
// ready == true
}
這樣執行緒在等待條件時也能響應停止請求,不必額外寫一個輪詢迴圈。
17.thread 和 jthread 對比
| 特性 | std::thread |
std::jthread |
|---|---|---|
| 標準版本 | C++11 | C++20 |
| 建立執行緒 | ✅ | ✅ |
手動 join() |
✅ | ✅ |
| 析構自動等待 | ❌ | ✅ |
| 析構時 joinable 會 terminate | ✅ | ❌ |
| 內建停止請求 | ❌ | ✅ |
自動傳 stop_token |
❌ | ✅ |
| 可 detach | ✅ | ✅ |
如果環境允許使用 C++20,新寫的“可停止後臺執行緒”通常優先考慮 jthread。
18.atomic<bool> running 和 stop token 怎麼選
傳統寫法:
std::atomic<bool> running{true};
while (running.load())
{
...
}
仍然完全合法,而且在簡單場景下很直觀。
stop_token 的優勢是把:
停止请求
停止观察
停止回调
jthread 生命周期
組織成標準協議。
因此:
- 只需要一個非常簡單的原子標誌:
atomic<bool>仍然很好用; - 執行緒本身有明確的“請求停止”生命週期:優先考慮
jthread + stop_token。
19.常見錯誤
- 以為
request_stop()會強制殺死執行緒。 - 工作函式從不檢查
stop_requested(),導致析構仍然一直等待。 - 線上程內部執行長時間不可中斷阻塞操作,導致停止響應很慢。
- 使用了
jthread卻仍然隨手detach(),失去 RAII 生命週期管理優勢。 - stop callback 內執行復雜阻塞邏輯或獲取大量鎖,導致停止路徑本身難以控制。
- 把“收到停止請求”理解成“必須立即在任意位置退出”,而不是在安全點協作退出。
20.小結
std::jthread是 C++20 更現代的執行緒管理類。jthread析構時會對 joinable 執行緒請求停止並等待結束。std::stop_token用來觀察停止請求,request_stop()只是請求,不是強制終止。- 停止必須由工作執行緒協作響應。
std::stop_source是請求端,std::stop_callback可以在請求發生時執行回撥。- 對需要明確停止協議的後臺執行緒,
jthread + stop_token通常比thread + atomic<bool>更結構化。