POSIX線程

POSIX線程（POSIX Threads，常被縮寫為pthreads）是POSIX的線程標準，定義了創建和操縱線程的一套API。

實現POSIX線程標準的庫常被稱作pthreads，一般用於Unix-like POSIX系統，如Linux、 Solaris。但是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_t：mutex數據類型
- 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_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的示例：

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

## 參考

- David R. Butenhof：Programming with POSIX Threads, Addison-Wesley, ISBN 0-201-63392-2
- Bradford Nichols，Dick Buttlar，Jacqueline Proulx Farell：Pthreads Programming, O'Reilly & Associates, ISBN 1-56592-115-1
- Charles J. Northrup：Programming with UNIX Threads, John Wiley & Sons, ISBN 0-471-13751-0
- Kay A. Robbins and Steven Robbins，UNIX Systems Programming, Prentice-Hall, ISBN 0-13-042411-0

## 外部連結

- Pthread Win-32，Basic Programming
- Pthreads Tutorial
- C/C++ Tutorial: using Pthreads
- Article " POSIX threads explained" by Daniel Robbins (Gentoo Linux創建者)
- Interview " Ten Questions with David Butenhof about Parallel Programming and POSIX Threads" by Michael Suess
- Open Source POSIX Threads for Win32
- The Open Group Base Specifications Issue 6, IEEE Std 1003.1
- GNU Portable threads
- Pthreads Presentation at 2007 OSCON (O'Reilly Open Source Convention) by Adrien Lamothe. An overview of Pthreads with current trends.