【linux】系統程式設計-5-執行緒
阿新 • • 發佈:2021-01-04
[toc]
---
## 前言
* [原文連結](https://www.cnblogs.com/lizhuming/p/14231490.html)
## 7. 執行緒
### 7.1 概念
* **程序:程序是資源管理的最小單位**
* **執行緒:執行緒是程式執行的最小單位**
* 因為程序開銷大,才衍生出執行緒
* 程序切換上下文時, 需要重新對映虛擬地址空間、進出OS核心、暫存器切換,還會干擾處理器的快取機制
* 一個程序至少需要一個執行緒作為它的指令執行體,程序管理著資源(比如cpu、記憶體、檔案等等), 而將執行緒分配到某個cpu上執行
* ![](https://img2020.cnblogs.com/blog/2085252/202101/2085252-20210104183328212-1597902459.png)
* 新的執行執行緒將擁有自己的棧,但與它的建立者共享全域性變數、檔案描述符、訊號處理函式和當前目錄狀態
* 特點:
* 一個程式至少有一個程序,一個程序至少有一個執行緒
* 執行緒使用程序的資源,程序崩潰,執行緒也隨之崩潰
* 執行緒切換上下文快,程序切換上下文慢
* 使用 **pthread_create** 建立執行緒
* 使用 **int pthread_attr_destroy(pthread_attr_t \*attr);** 函式來銷燬一個執行緒屬性物件
* 使用 **pthread_attr_init()** 函式可以初始化執行緒物件的屬性
* 使用 **pthread_join()** 等待執行緒結束
* 使用 **pthread_detach()** 來設定執行緒為分離狀態
### 7.2 建立執行緒
#### 7.2.1 pthread_create()
* 使用 **pthread_create** 建立執行緒
* 通過命令 **man** 瞭解更多
* 所需要的標頭檔案:
```c
#include
```
* 函式原型:**`int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);`**
* **thread**:指向執行緒識別符號的指標
* **attr**:設定執行緒屬性
* **start_routine**:函式指標,執行緒入口
* **arg**:傳給執行緒入口函式的引數
### 7.3 設定執行緒屬性
* 執行緒屬性結構體 **pthread_attr_t**:
```c
typedef struct{
int etachstate; //執行緒的分離狀態
int schedpolicy; //執行緒排程策略
structsched_param schedparam; //執行緒的排程引數
int inheritsched; //執行緒的繼承性
int scope; //執行緒的作用域
size_t guardsize; //執行緒棧末尾的警戒緩衝區大小
int stackaddr_set; //執行緒的棧設定
void* stackaddr; //執行緒棧的位置
size_t stacksize; //執行緒棧的大小
}pthread_attr_t;
```
* *注意:*
* *因為 **pthread** 並非 **Linux** 系統的預設庫,而是 **POSIX執行緒庫**。在Linux中將其作為一個庫來使用, 因此編譯時需要加上-lpthread(或-pthread)以顯式指定連結該庫*
* *函式在執行錯誤時,並不把錯誤資訊寫到變數 **error** 中,而是作為返回值返回*
* 執行緒屬性不能直接設定,只能通過函式操作
* 執行緒屬性包括:作用域(scope)、棧大小(stacksize)、棧地址(stackaddress)、優先順序(priority)、 分離的狀態(detachedstate)、排程策略和引數(scheduling policy and parameters)
* 執行緒的預設屬性:非繫結、非分離、1M的堆疊大小、與父程序同樣級別的優先順序
* API:
|API|說明|
|:-:|:-:|
|pthread_attr_init()|初始化一個執行緒物件的屬性|
|pthread_attr_destroy()|銷燬一個執行緒屬性物件|
|pthread_attr_getaffinity_np()|獲取執行緒間的CPU親緣性|
|pthread_attr_setaffinity_np()|設定執行緒的CPU親緣性|
|pthread_attr_getdetachstate()|獲取執行緒分離狀態屬性|
|pthread_attr_setdetachstate()|修改執行緒分離狀態屬性|
|pthread_attr_getguardsize()|獲取執行緒的棧保護區大小|
|pthread_attr_setguardsize()|設定執行緒的棧保護區大小|
|pthread_attr_getscope()|獲取執行緒的作用域|
|pthread_attr_setscope()|設定執行緒的作用域|
|pthread_attr_getstack()|獲取執行緒的堆疊資訊(棧地址和棧大小)|
|pthread_attr_setstack()|設定執行緒堆疊區|
|pthread_attr_getstacksize()|獲取執行緒堆疊大小|
|pthread_attr_setstacksize()|設定執行緒堆疊大小|
|pthread_attr_getschedpolicy()|獲取執行緒的排程策略|
|pthread_attr_setschedpolicy()|設定執行緒的排程策略|
|pthread_attr_setschedparam()|獲取執行緒的排程優先順序|
|pthread_attr_getschedparam()|設定執行緒的排程優先順序|
|pthread_attr_getinheritsched()|獲取執行緒是否繼承排程屬性|
|pthread_attr_getinheritsched()|設定執行緒是否繼承排程屬性|
#### 7.3.1 pthread_attr_init()
* 使用 **pthread_attr_init** 初始化執行緒物件屬性
* 通過命令 **man** 瞭解更多
* 所需要的標頭檔案:
```c
#include
```
* 函式原型:**`int pthread_attr_init(pthread_attr_t *attr);`**
* **attr**:指向一個執行緒屬性的指標
* 返回:
* 成功:返回 0
* 失敗:返回 非 0
#### 7.3.2 銷燬一個執行緒屬性物件
* 使用 **`int pthread_attr_destroy(pthread_attr_t *attr);`** 函式來銷燬一個執行緒屬性物件
* **attr**:指向一個執行緒屬性的指標
* 返回:
* 成功:返回 0
* 失敗:返回 錯誤碼
#### 7.3.3 執行緒的分離狀態
* 在任何一個時間點上,執行緒是可結合的(joinable),或者是分離的(detached)
* 可結合的執行緒:能夠被其他執行緒收回其資源和殺死;在被其他執行緒回收之前,它的儲存器資源(如棧)是不釋放的
* 分離的執行緒:不能被其他執行緒回收或殺死的,它的儲存器資源在它終止時由系統自動釋放
* 執行緒的分離狀態決定一個執行緒以什麼樣的方式來終止自己
* 預設狀態下是 **非分離狀態**
* 函式:
* **`int pthread_join(pthread_t tid, void **rval_ptr);`**
* 等待某個執行緒的終止,獲得該執行緒的終止狀態,並收回所佔的資源
* * **tid**:執行緒識別符號
* rval_ptr設定為NULL,則忽略返回狀態
* **`int pthread_detach(pthread_t tid);`**
* 將執行緒設定為分離狀態
* 標頭檔案:**`#include `**
* **tid**:執行緒識別符號
* 返回:
* 成功:返回 0
* 失敗:返回一個錯誤值:
* EINVAL:tid 對應的執行緒不是一個 非分離的執行緒
* ESRCH:找不到對應的執行緒
* 在**非分離狀態**下:
* 原有執行緒等待建立的執行緒結束,只有當 **pthread_join()** 函式放回時,建立的執行緒才算真正終止
* 在**分離狀態**下:
* 該執行緒沒有被其他的執行緒所等待,自己執行結束了,執行緒也就終止了,馬上釋放系統資源
* 如果在建立執行緒時就知道不需要了解執行緒的終止狀態,則可以 **pthread_attr_t** 結構中的 **detachstate** 執行緒屬性,讓執行緒以分離狀態啟動
* 使用 **`pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)`** 函式來設定執行緒分離狀態
* **attr**:指向一個執行緒屬性的指標
* **detachstate**:
* **PTHREAD_CREATE_DETACHED**:分離執行緒
* **PTHREAD _CREATE_JOINABLE**:非分離執行緒
* 獲取某個執行緒的分離狀態
* 使用 **`int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);`** 函式獲取執行緒的分離狀態
* **attr**:指向一個執行緒屬性的指標
* **detachstate**:儲存狀態的值
* 返回:
* 成功:返回 0
* 失敗:返回 錯誤碼
* ***注意:***
* *如果設定一個執行緒為分離執行緒,而這個執行緒執行又非常快,它很可能在 **pthread_create** 函式返回之前就終止了,它終止以後就可能將執行緒號和系統資源移交給其他的執行緒使用,這樣呼叫 **pthread_create** 的執行緒就得到了錯誤的執行緒號。要避免這種情況可以採取一定的同步措施,最簡單的方法之一是可以在被建立的執行緒裡呼叫 **pthread_cond_timewait** 函式,讓這個執行緒等待一會兒,留出足夠的時間讓函式 **pthread_create** 返回。設定一段等待時間,是在多執行緒程式設計裡常用的方法。但是注意不要使用諸如 **wait()** 之類的函式,它們是使整個程序睡眠,並不能解決執行緒同步的問題。*
#### 7.3.4 執行緒的排程策略
* POSIX 標準指定了三種排程策略:
1. 普通執行緒(預設)**SCHED_OTHER**。採用時分排程策略,不需要實時機制。另外兩種用於超級使用者許可權時執行。
2. 實時執行緒**SCHDE_FIFO**。採用實時排程-先進先出方式策略。一旦佔用cpu則一直執行,直到有更高優先順序任務到達或自己放棄。
3. 輪詢排程**SCHED_RR**。採用實時排程-時間片輪轉方式排程策略。當任務的時間片用完,系統將重新分配時間片,並置於就緒佇列尾。放在佇列尾保證了所有具有相同優先順序的RR任務的排程公平。所以說SCHED_RR=SCHED_OTHER+SCHDE_FIFO。
* 與排程相關的API
* 引數說明:
* attr:指向一個執行緒屬性的指標。
* inheritsched:執行緒是否繼承排程屬性,可選值分別為
* PTHREAD_INHERIT_SCHED:排程屬性將繼承於建立的執行緒,attr中設定的排程屬性將被忽略。
* PTHREAD_EXPLICIT_SCHED:排程屬性將被設定為attr中指定的屬性值。
* policy:可選值為執行緒的三種排程策略
* SCHED_OTHER
* SCHED_FIFO
* SCHED_RR
```c
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inheritsched);
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);
```
#### 7.3.5 執行緒優先順序
* SCHED_OTHER排程時
* 靜態優先順序必須置為 **0**
* 處於靜態優先順序為 **0** 的執行緒按照動態優先順序被排程
* 動態優先順序值起始於 **nice** 值,且當前執行緒處於就緒態並被排程器無視時,其動態值++,以保證競爭CPU的公平性
* 執行緒優先順序的設定(靜態優先順序)(SCHED_FIFO和SCHED_RR)
* 通過以下函式來獲得執行緒可以設定的最高和最低優先順序(不支援SCHED_OTHER)
```c
int sched_get_priority_max(int policy);
int sched_get_priority_min(int policy);
```
* 通過以下兩個函式來設定和獲取優先順序
```c
int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);
```
* 執行緒優先順序特點
* 新執行緒的優先順序預設為 0
* 新執行緒不繼承父執行緒排程優先順序(PTHREAD_EXPLICIT_SCHED)
* 當執行緒的排程策略為SCHED_OTHER時,不允許修改執行緒優先順序,僅當排程策略為實時(即SCHED_FIFO或SCHED_RR)時才有效, 並可以在執行時通過pthread_setschedparam()函式來改變,預設為0。
#### 7.3.6 執行緒棧
* 執行緒棧:
* 用於存放函式形參、區域性變數、執行緒切換現場暫存器等資料。
* 使用的是程序的地址空間,預設執行緒棧大小是 **1M**
* 設定和獲取執行緒大小可以使用一下函式
```c
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
```
#### 7.4 退出執行緒
* 執行緒退出使用 **`void pthread_exit(void *retval);`** 函式
* 注意:不能使用 **exit()** 函式退出,因為該函式是直接退出程序的,會把程序內所有函式都退出。
## 參考