【linux】系統程式設計-5-執行緒

李柱明發表於2021-01-04


前言

7. 執行緒

7.1 概念

  • 程式:程式是資源管理的最小單位

  • 執行緒:執行緒是程式執行的最小單位

  • 因為程式開銷大,才衍生出執行緒

    • 程式切換上下文時, 需要重新對映虛擬地址空間、進出OS核心、暫存器切換,還會干擾處理器的快取機制
  • 一個程式至少需要一個執行緒作為它的指令執行體,程式管理著資源(比如cpu、記憶體、檔案等等), 而將執行緒分配到某個cpu上執行

  • 新的執行執行緒將擁有自己的棧,但與它的建立者共享全域性變數、檔案描述符、訊號處理函式和當前目錄狀態

  • 特點:

    • 一個程式至少有一個程式,一個程式至少有一個執行緒
    • 執行緒使用程式的資源,程式崩潰,執行緒也隨之崩潰
    • 執行緒切換上下文快,程式切換上下文慢
  • 使用 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 瞭解更多
  • 所需要的標頭檔案:
    #include <pthread.h>
    
  • 函式原型: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
    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 瞭解更多
  • 所需要的標頭檔案:
    #include <pthread.h>
    
  • 函式原型: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 <pthread.h>
      • 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
    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)
    int sched_get_priority_max(int policy);
    int sched_get_priority_min(int policy);
    
    • 通過以下兩個函式來設定和獲取優先順序
    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
    • 設定和獲取執行緒大小可以使用一下函式
      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() 函式退出,因為該函式是直接退出程式的,會把程式內所有函式都退出。

參考

  * 野火

相關文章