第 22.19.6 節

jthread 與 stop_token

0瀏覽次數0訪問次數--跳出率--平均停留

C++20 提供了:

std::jthread

它可以理解成“更安全的 std::thread”:

  • 析構時自動處理線程,不要求每條控制路徑都手寫 join()
  • 內置協作式停止機制;
  • 可以與 std::stop_tokenstd::stop_sourcestd::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. threadjthread 對比

特性std::threadstd::jthread
標準版本C++11C++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. 常見錯誤

  1. 以爲 request_stop() 會強制殺死線程。
  2. 工作函數從不檢查 stop_requested(),導致析構仍然一直等待。
  3. 在線程內部執行長時間不可中斷阻塞操作,導致停止響應很慢。
  4. 使用了 jthread 卻仍然隨手 detach(),失去 RAII 生命週期管理優勢。
  5. stop callback 內執行復雜阻塞邏輯或獲取大量鎖,導致停止路徑本身難以控制。
  6. 把“收到停止請求”理解成“必須立即在任意位置退出”,而不是在安全點協作退出。

小結

  • std::jthread 是 C++20 更現代的線程管理類。
  • jthread 析構時會對 joinable 線程請求停止並等待結束。
  • std::stop_token 用來觀察停止請求,request_stop() 只是請求,不是強制終止。
  • 停止必須由工作線程協作響應。
  • std::stop_source 是請求端,std::stop_callback 可以在請求發生時執行回調。
  • 對需要明確停止協議的後臺線程,jthread + stop_token 通常比 thread + atomic<bool> 更結構化。
音乐页