std::thread執行緒詳解(1)
阿新 • • 發佈:2021-01-02
## 目錄
- [目錄](#目錄)
- [簡介](#簡介)
- [執行緒的使用](#執行緒的使用)
- [執行緒的建立](#執行緒的建立)
- [執行緒的方法和屬性](#執行緒的方法和屬性)
- [std::jthread (C++20)](#stdjthread-c20)
- [stop_token (C++20)](#stop_token-c20)
- [總結](#總結)
- [Ref](#ref)
## 簡介
本文主要介紹了標準庫中的執行緒部分。執行緒是目前多核程式設計裡面最重要的一部分。
與程序程序相比,其所需的資源更少,執行緒之間溝通的方法更多; 他們之間的區別可以比較簡明用以下幾點概括[1]:
1. 程序是資源分配的最小單位,執行緒是CPU排程的最小單位;也就是說程序之間的資源是相互隔離,而執行緒之間的是可以相互訪問的。
2. 執行緒的存在依賴於程序,一個程序可以保護多個執行緒;
3. 程序出現錯誤不會影響其他程序,但是一個執行緒出現錯誤,會影響同一程序下的所有執行緒。
## 執行緒的使用
### 執行緒的建立
一般使用`std::thread`建立一個執行緒。`std::thread`支援輸入一個函式物件,及一些引數,類似於`std::bind`,不過沒有佔位符。
最常見,最簡單的是對輸入一個匿名函式作為引數:
```c++
std::thread t1([]() {
std::cout << "Hello World" << std::endl;
});
t1.join();
```
需要注意的是,在使用多執行緒的時候,如果使用類似於`std::cout << "Hello World" << std::endl;`的語句,容易造成輸出的混亂。比如
```C++
std::thread t1([]() {
std::cout << "Hello World1" << std::endl;
});
std::thread t2([]() {
std::cout << "Hello World2" << std::endl;
});
t1.join();
t2.join();
```
以上程式碼,我們一般來說期望的輸出是
![期望的輸出](https://img2020.cnblogs.com/blog/2105008/202101/2105008-20210101210755155-528460963.png)
但是,在一些情況下,它還會以以下的方法輸出
![錯誤的輸出](https://img2020.cnblogs.com/blog/2105008/202101/2105008-20210101210755069-745182546.png)
造成這個的原因很簡單,因為`"Hello World"`和`std::endl`的輸出是分開的,所以他們之間可能被插入其他的輸出。為了解決這個問題。一般會使用一個完整的字串進行輸出,但是C++在格式化這一方面做的比較差(`C++20`的`format`庫看起來還不錯),所以一般情況下會使用`printf`輸出。
### 執行緒的方法和屬性
1. `joinable()`判斷執行緒是否可連線(可執行執行緒)的,有以下情況的,為不可連線的:
1. 構造時,`thread()`沒有引數;
2. 該物件的執行緒已經被移動了;
3. 該執行緒已經被`join`或`detach`;
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_stop`和`join`。
除此之外,`std::jthread`還提供了一個內建的`std::stop_token`。可以通過執行緒函式的第一個引數來獲取(如果函式的第一個引數型別為`std::stop_token`)。
可以通過`get_stop_source`、`get_stop_token`、`request_stop`等方法來對其進行操作。
### stop_token (C++20)
`stop_token`類似於一個訊號,告訴執行緒是否到了結束的時候。和`stop_source`一起使用。`stop_token`用來獲取是否退出(讀),而`stop_source`用來請求推出(讀寫)。其方法:
1. `request_stop` 請求退出
2. `stop_requested` 獲取是否已經請求退出
3. `stop_possible` 獲取是否可以請求退出
樣例:
```C++
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](https://img2020.cnblogs.com/blog/2105008/202101/2105008-20210101210755062-1194481525.png)
## 總結
本次講述了執行緒建立的一些方法,可以看到相比較C語言而言,由於C++11提出的函式物件(普通函式、匿名函式,`std::bind`的輸出等)使得執行緒的建立更加的方便。
下一次將講述執行緒之間的通訊。在C++中,執行緒之間的通訊方法和C語言提供的類似,不過是將其包裝了一下。
## Ref
[1] https://www.zhihu.com/question/