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'交替出現。

參考

參見

外部連結