std::thread線程詳解(1)

目錄

簡介

本文主要介紹了標準庫中的線程部分。線程是目前多核編程裏面最重要的一部分。
與進程進程相比,其所需的資源更少,線程之間溝通的方法更多; 他們之間的區別可以比較簡明用以下幾點概括[1]:

  1. 進程是資源分配的最小單位,線程是CPU調度的最小單位;也就是說進程之間的資源是相互隔離,而線程之間的是可以相互訪問的。
  2. 線程的存在依賴於進程,一個進程可以保護多個線程;
  3. 進程出現錯誤不會影響其他進程,但是一個線程出現錯誤,會影響同一進程下的所有線程。

線程的使用

線程的創建

一般使用std::thread創建一個線程。std::thread支持輸入一個函數對象,及一些參數,類似於std::bind,不過沒有佔位符。

最常見,最簡單的是對輸入一個匿名函數作為參數:

std::thread t1([]() {
  std::cout << "Hello World" << std::endl;
});
t1.join();

需要注意的是,在使用多線程的時候,如果使用類似於std::cout << "Hello World" << std::endl;的語句,容易造成輸出的混亂。比如

std::thread t1([]() {
  std::cout << "Hello World1" << std::endl;
});
std::thread t2([]() {
  std::cout << "Hello World2" << std::endl;
});
t1.join();
t2.join();

以上代碼,我們一般來說期望的輸出是

期望的輸出

但是,在一些情況下,它還會以以下的方法輸出

錯誤的輸出

造成這個的原因很簡單,因為"Hello World"std::endl的輸出是分開的,所以他們之間可能被插入其他的輸出。為了解決這個問題。一般會使用一個完整的字符串進行輸出,但是C++在格式化這一方面做的比較差(C++20format庫看起來還不錯),所以一般情況下會使用printf輸出。

線程的方法和屬性

  1. joinable()判斷線程是否可連接(可執行線程)的,有以下情況的,為不可連接的:

    1. 構造時,thread()沒有參數;
    2. 該對象的線程已經被移動了;
    3. 該線程已經被joindetach
  2. get_id() 返回線程的ID;

  3. native_handle() 返回POSIX標準的線程對象;

  4. join() 等待線程執行完成;

  5. detach() 分離線程,分離後對象不再擁有線程。該線程結束後,會自動回收內存。(並不會開啟另一個進程);

  6. swap() 交換對象的線程。

std::jthread (C++20)

除了常用的std::thread外,標準庫還存在着另一個可以創建線程的類,std::jthread。他們之間的差別比較明顯的就是,std::jthread會在解構的時候判斷線程是否還在運行joinable,如果還在運行則自動調用request_stopjoin

除此之外,std::jthread還提供了一個內置的std::stop_token。可以通過線程函數的第一個參數來獲取(如果函數的第一個參數類型為std::stop_token)。

可以通過get_stop_sourceget_stop_tokenrequest_stop等方法來對其進行操作。

stop_token (C++20)

stop_token類似於一個信號,告訴線程是否到了結束的時候。和stop_source一起使用。stop_token用來獲取是否退出(讀),而stop_source用來請求推出(讀寫)。其方法:

  1. request_stop 請求退出

  2. stop_requested 獲取是否已經請求退出

  3. stop_possible 獲取是否可以請求退出

樣例:

void thread_func(std::stop_token token) {
    int data = 0;
    while (!token.stop_requested()) {
        printf("%d\n", data);
        data++;
        std::this_thread::sleep_for(1s);
    }
    printf("Exit\n");
}

int main() {
    std::jthread mythread(thread_func);

    std::this_thread::sleep_for(4s);

    return 0;
}

輸出:

jthread

總結

本次講述了線程創建的一些方法,可以看到相比較C語言而言,由於C++11提出的函數對象(普通函數、匿名函數,std::bind的輸出等)使得線程的創建更加的方便。

下一次將講述線程之間的通信。在C++中,線程之間的通信方法和C語言提供的類似,不過是將其包裝了一下。

Ref

[1] //www.zhihu.com/question/25532384

Tags: