POSIX執行緒

POSIX執行緒(英語:POSIX Threads,常被縮寫為pthreads)是POSIX執行緒標準,定義了建立和操縱執行緒的一套API

實現POSIX執行緒標準的庫常被稱作pthreads,一般用於Unix-like POSIX系統,如LinuxSolaris。但是Microsoft Windows上的實現也存在,例如直接使用Windows API實現的第三方庫pthreads-w32;而利用Windows的SFU/SUA子系統,則可以使用微軟提供的一部分原生POSIX API。

API具體內容

Pthreads定義了一套C語言的類型、函式與常數,它以pthread.h頁面存檔備份,存於網際網路檔案館標頭檔和一個執行緒庫實現。

Pthreads API中大致共有100個函式呼叫,全都以"pthread_"開頭,並可以分為四類:

  • 執行緒管理,例如建立執行緒,等待(join)執行緒,查詢執行緒狀態等。
  • 互斥鎖(Mutex):建立、摧毀、鎖定、解鎖、設定屬性等操作
  • 條件變數(Condition Variable):建立、摧毀、等待、通知、設定與查詢屬性等操作
  • 使用了互斥鎖的執行緒間的同步管理

POSIX的Semaphore API可以和Pthreads協同工作,但這並不是Pthreads的標準。因而這部分API是以"sem_"打頭,而非"pthread_"。

資料類型

  • pthread_t:執行緒控制代碼。出於移植目的,不能把它作為整數處理,應使用函式pthread_equal()對兩個執行緒ID進行比較。取得自身所線上程id使用函式pthread_self()。
  • pthread_attr_t:執行緒屬性。主要包括scope屬性、detach屬性、堆疊位址、堆疊大小、優先級。主要屬性的意義如下:
    • __detachstate,表示新執行緒是否與行程中其他執行緒脫離同步。如果設定為PTHREAD_CREATE_DETACHED,則新執行緒不能用pthread_join()來同步,且在退出時自行釋放所占用的資源。預設為PTHREAD_CREATE_JOINABLE狀態。可以線上程建立並執行以後用pthread_detach()來設定。一旦設定為PTHREAD_CREATE_DETACHED狀態,不論是建立時設定還是執行時設定,則不能再恢復到PTHREAD_CREATE_JOINABLE狀態。
    • __schedpolicy,表示新執行緒的排程策略,包括SCHED_OTHER(正常、非即時)、SCHED_RR(即時、輪轉法)和SCHED_FIFO(即時、先入先出)三種,預設為SCHED_OTHER,後兩種排程策略僅對超級使用者有效。執行時可以用過pthread_setschedparam()來改變。
    • __schedparam,一個struct sched_param結構,目前僅有一個sched_priority整型變數表示執行緒的執行優先級。這個參數僅當排程策略為即時(即SCHED_RR或SCHED_FIFO)時才有效,並可以在執行時通過pthread_setschedparam()函式來改變,預設為0。系統支援的最大和最小的優先級值可以用函式sched_get_priority_max和sched_get_priority_min得到。
    • __inheritsched,有兩種值可供選擇:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新執行緒使用顯式指定排程策略和排程參數(即attr中的值),而後者表示繼承呼叫者執行緒的值。預設為PTHREAD_EXPLICIT_SCHED。
    • __scope,表示執行緒間競爭CPU的範圍,也就是說執行緒優先級的有效範圍。POSIX的標準中定義了兩個值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示與系統中所有執行緒一起競爭CPU時間,後者表示僅與同行程中的執行緒競爭CPU。目前LinuxThreads僅實現了PTHREAD_SCOPE_SYSTEM一值。
  • pthread_barrier_t同步屏障資料類型
  • pthread_mutex_tmutex資料類型
  • pthread_cond_t條件變數資料類型

函式

執行緒操縱函式(簡介起見,省略參數):

  • pthread_create():建立一個執行緒
  • pthread_exit():終止當前執行緒
  • pthread_cancel():請求中斷另外一個執行緒的執行。被請求中斷的執行緒會繼續執行,直至到達某個取消點(Cancellation Point)。取消點是執行緒檢查是否被取消並按照請求進行動作的一個位置。POSIX 的取消類型(Cancellation Type)有兩種,一種是延遲取消(PTHREAD_CANCEL_DEFERRED),這是系統預設的取消類型,即線上程到達取消點之前,不會出現真正的取消;另外一種是非同步取消(PHREAD_CANCEL_ASYNCHRONOUS),使用非同步取消時,執行緒可以在任意時間取消。系統呼叫的取消點實際上是函式中取消類型被修改為非同步取消至修改回延遲取消的時間段。幾乎可以使執行緒掛起的庫函式都會回應CANCEL訊號,終止執行緒,包括sleep、delay等延時函式。
  • pthread_join():阻塞當前的執行緒,直到另外一個執行緒執行結束
  • pthread_kill():向指定ID的執行緒傳送一個訊號,如果執行緒不處理該訊號,則按照訊號預設的行為作用於整個行程。訊號值0為保留訊號,作用是根據函式的返回值判斷執行緒是不是還活著。
  • pthread_cleanup_push():執行緒可以安排異常退出時需要呼叫的函式,這樣的函式稱為執行緒清理程式,執行緒可以建立多個清理程式。執行緒清理程式的入口位址使用棧儲存,實行先進後處理原則。由pthread_cancel或pthread_exit引起的執行緒結束,會次序執行由pthread_cleanup_push壓入的函式。執行緒函式執行return語句返回不會引起執行緒清理程式被執行。
  • pthread_cleanup_pop():以非0參數呼叫時,引起當前被彈出的執行緒清理程式執行。
  • pthread_setcancelstate():允許或禁止取消另外一個執行緒的執行。
  • pthread_setcanceltype():設定執行緒的取消類型為延遲取消或非同步取消。

執行緒屬性函式:

  • pthread_attr_init():初始化執行緒屬性變數。執行後,pthread_attr_t結構所包含的內容是作業系統支援的執行緒的所有屬性的預設值。
  • pthread_attr_setdetachstate():設定執行緒屬性變數的detachstate屬性(決定執行緒在終止時是否可以被joinable)
  • pthread_attr_getdetachstate():取得脫離狀態的屬性
  • pthread_attr_setscope():設定執行緒屬性變數的__scope屬性
  • pthread_attr_setschedparam():設定執行緒屬性變數的schedparam屬性,即呼叫的優先級。
  • pthread_attr_getschedparam():取得執行緒屬性變數的schedparam屬性,即呼叫的優先級。
  • pthread_attr_destroy():刪除執行緒的屬性,用無效值覆蓋

mutex函式:

  • pthread_mutex_init() 初始化互斥鎖
  • pthread_mutex_destroy() 刪除互斥鎖
  • pthread_mutex_lock():占有互斥鎖(阻塞操作)
  • pthread_mutex_trylock():試圖占有互斥鎖(不阻塞操作)。即,當互斥鎖空閒時,將占有該鎖;否則,立即返回。
  • pthread_mutex_unlock(): 釋放互斥鎖
  • pthread_mutexattr_(): 互斥鎖屬性相關的函式

條件變數函式:

  • pthread_cond_init():初始化條件變數
  • pthread_cond_destroy():銷毀條件變數
  • pthread_cond_signal(): 傳送一個訊號給正在當前條件變數的執行緒佇列中處於阻塞等待狀態的執行緒,使其脫離阻塞狀態,喚醒後繼續執行。如果沒有執行緒處在阻塞等待狀態,pthread_cond_signal也會成功返回。一般只給一個阻塞狀態的執行緒發訊號。假如有多個執行緒正在阻塞等待當前條件變數,則根據各等待執行緒優先級的高低確定哪個執行緒接收到訊號開始繼續執行。如果各執行緒優先級相同,則根據等待時間的長短來確定哪個執行緒獲得訊號。但pthread_cond_signal在多處理器上可能同時喚醒多個執行緒,當只能讓一個被喚醒的執行緒處理某個任務時,其它被喚醒的執行緒就需要繼續wait。POSIX規範要求pthread_cond_signal至少喚醒一個pthread_cond_wait上的執行緒,有些實現為了簡便,在單處理器上也會喚醒多個執行緒。所以最好對pthread_cond_wait()使用while迴圈對條件變數是否滿足做條件判斷。
  • pthread_cond_wait(): 等待條件變數的特殊條件發生;pthread_cond_wait() 必須與一個pthread_mutex配套使用。該函式呼叫實際上依次做了3件事:對當前pthread_mutex解鎖、把當前執行緒掛起到當前條件變數的執行緒佇列、被其它執行緒的訊號喚醒後對當前pthread_mutex申請加鎖。如果執行緒收到一個訊號被喚醒,將被配套的互斥鎖重新鎖住,pthread_cond_wait() 函式將不返回直到執行緒獲得配套的互斥鎖。需要注意的是,一個條件變數不應該與多個互斥鎖配套使用。
  • pthread_cond_broadcast(): 某些應用,如執行緒池,pthread_cond_broadcast喚醒全部執行緒,但我們通常只需要一部分執行緒去做執行任務,所以其它的執行緒需要繼續wait.
  • pthread_condattr_(): 條件變數屬性相關的函式

執行緒私有儲存(Thread-local storage):

  • pthread_key_create(): 分配用於標識行程中執行緒特定資料的pthread_key_t類型的鍵
  • pthread_key_delete(): 銷毀現有執行緒特定資料鍵
  • pthread_setspecific(): 為指定執行緒的特定資料鍵設定繫結的值
  • pthread_getspecific(): 取得呼叫執行緒的鍵繫結值,並將該繫結儲存在 value 指向的位置中

同步屏障函式

  • pthread_barrier_init(): 同步屏障初始化
  • pthread_barrier_wait():
  • pthread_barrier_destory():

其它多執行緒同步函式:

  • pthread_rwlock_*(): 讀寫鎖

工具函式:

  • pthread_equal(): 對兩個執行緒的執行緒標識號進行比較
  • pthread_detach(): 分離執行緒
  • pthread_self(): 查詢執行緒自身執行緒標識號
  • pthread_once(): 某些需要僅執行一次的函式。其中第一個參數為pthread_once_t類型,是內部實現的互斥鎖,保證在程式全域僅執行一次。

號誌函式,包含在semaphore.h中:

  • sem_open:建立或者打開已有的命名號誌。可分為二值號誌與計數號誌。命名號誌可以在行程間共享使用。
  • sem_close:關閉一個訊號燈,但沒有將它從系統中刪除。命名訊號燈是隨核心持續的,即使當前沒有行程打開著某個訊號燈,它的值仍然保持。
  • sem_unlink:從系統中刪除訊號燈。
  • sem_getvalue:返回所指定訊號燈的當前值。如果該訊號燈當前已上鎖,那麼返回值或為0,或為某個負數,其絕對值就是等待該訊號燈解鎖的執行緒數。
  • sem_wait:申請共享資源,所指定訊號燈的值如果大於0,那就將它減1並立即返回,就可以使用申請來的共享資源了。如果該值等於0,呼叫執行緒就被進入睡眠狀態,直到該值變為大於0,這時再將它減1,函式隨後返回。sem_wait操作必須是原子操作。
  • sem_trywait:申請共享資源,當所指定訊號燈的值已經是0時,後者並不將呼叫執行緒投入睡眠。相反,它返回一個EAGAIN錯誤。
  • sem_post:釋放共享資源。與sem_wait恰相反。
  • sem_init:初始化非命名(主記憶體)號誌
  • sem_destroy:摧毀非命名號誌

共享主記憶體函式,包含在sys/mman.h中,連結時使用rt庫:

  • mmap:把一個檔案或一個POSIX共享主記憶體區對象對映到呼叫行程的位址空間。使用該函式的目的: 1.使用普通檔案以提供主記憶體對映I/O 2.使用特殊檔案以提供匿名主記憶體對映。 3.使用shm_open以提供無親緣關係行程間的Posix共享主記憶體區。
  • munmap: 刪除一個對映關係
  • msync:檔案與主記憶體同步函式
  • shm_open:建立或打開共享主記憶體區
  • shm_unlink:刪除一個共享主記憶體區對象的名字,刪除一個名字僅僅防止後續的open,msq_open或sem_open呼叫取得成功。
  • ftruncate:調整檔案或共享主記憶體區大小
  • fstat來取得有關該對象的資訊

例子

C中使用 Pthreads的範例:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>

static void wait(void) {
    time_t start_time = time(NULL);

    while (time(NULL) == start_time) {
        /* do nothing except chew CPU slices for up to one second */
    }
}

static void *thread_func(void *vptr_args) {
    int i;

    for (i = 0; i < 20; i++) {
        fputs("  b\n", stderr);
        wait();
    }

    return NULL;
}

int main(void) {
    int i;
    pthread_t thread;

    if (pthread_create(&thread, NULL, thread_func, NULL) != 0) {
        return EXIT_FAILURE;
    }

    for (i = 0; i < 20; i++) {
        puts("a");
        wait();
    }

    if (pthread_join(thread, NULL) != 0) {
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

這段程式建立了一個新執行緒,列印含有「b」的行,主執行緒列印含有「a」的行。當兩個執行緒相互切換執行時輸出結果為'a'和'b'交替出現。

參考

參見

外部連結