---
# System prepended metadata

title: POSIX Thread 介紹

---

# POSIX Thread 介紹

POSIX Threads 是一套符合 [POSIX 標準](https://zh.wikipedia.org/zh-tw/%E5%8F%AF%E7%A7%BB%E6%A4%8D%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E6%8E%A5%E5%8F%A3)的 API ，方便開發者設計出 User-level 的多執行緒程式。

## 開始之前
先了解執行緒的記憶體分配有助於多執行緒程式的開發。
### thread 空間分配

在同一個 Program 中，多個 Thread 會共用同一個位址空間，每個 Thread 都會分配到一塊空間作為自己的 Stack ，而指向這些空間起始點的指標就被稱為 `Stack pointer` 。

> 呼叫函式和一般的跳躍不同，在呼叫結束後必須回到原本呼叫的地方，原本執行中的位址被叫做「回傳位址」（return address）。如果說呼叫只會發生一次的話，隨便找一個暫存器存回傳位址就好了；但是函式呼叫可以一層一層呼叫下去，所以必須把回傳位址存在記憶體裡。實務上，回傳位址被存在記憶體中的堆疊（stack）裡。
堆疊，被實作成只能使用堆疊空間最上方位址所存的一個變數。而這個紀錄堆疊最上方的紀錄空間被稱為「堆疊指標」（stack pointer）。x86-64 中，為了方便寫呼叫函式的程式，提供了堆疊指標專用的暫存器，和使用這個暫存器的指令。往堆疊上堆資料的操作是「push」，而取出堆疊資料的操作是「pop」。
-- [C編譯器入門～想懂低階系統從自幹編譯器開始～](https://koshizuow.gitbook.io/compilerbook/machine_code_assembler/c_assembly/kansuu_yobidashi)

當執行緒呼叫其他函式時， stack pointer 便會向下移動，這讓我們可以有更多空間去存放參數以及局部變數。
當函式執行完畢並返回時， stack pointer 便會移動到原先的位址。
> 舊的 stack pointer 紀錄的地址也會被存放在 Stack 中，這也是函式可以快速返回的原因。

![](https://i.imgur.com/F6ZmruQ.jpg)

![](https://www.codeproject.com/KB/mcpp/5256936/stack-r-700.png)
> [ref](https://www.codeproject.com/Articles/5256936/Pointers)

對於函式的流程控制，這部[The Call Stack](https://www.youtube.com/watch?v=Q2sFmqvpBe0)影片有詳細的解說。
## 進入正題

Pthreads API 中大致共有 100 個函數調用，全都以 **pthread_** 開頭，並可以分為四類：

- 執行緒管理，例如創建執行緒，等待 ( join ) 執行緒，查詢執行緒狀態等。
- 互斥鎖（Mutex）：創建、摧毀、鎖定、解鎖、設置屬性等操作
- 條件變量（Condition Variable）：創建、摧毀、等待、通知、設置與查詢屬性等操作
- 使用了互斥鎖的執行緒間的同步管理
> POSIX 的 Semaphore API 可以和 Pthreads 協同工作，但這並不是 Pthreads 的標準。因而這部分API是以 **sem_** 打頭，而非 **pthread_**。
> -- [wikipedia](https://zh.wikipedia.org/wiki/POSIX%E7%BA%BF%E7%A8%8B)

而本篇文章要介紹的是第一項： 執行緒管理的部分。
### 創建新的執行緒

我們可以利用 POSIX Thread 創建具有一個執行緒以上的 Process ，第一個 Thread 會負責運行 `main()` 中的程式碼。若要創建一個以上的執行緒，我們可以使用 `pthread_create` : 
```c=
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);
```

其中 `void *(*start_routine) (void *)` 用語言表達的話，可以解釋成:
> 一個指標它帶有一個指向 void 型態資料的指標，並且，它會返回指向 void 型態資料的指標。

> 如果仍無法理解上述的程式碼，建議讀者可以去複習[重拾 C 語言::函式指標](https://hackmd.io/@learnc/B1E2HEmz_)。

看完 `posix_create` 的定義以後，可以看看以下範例:

```c=
#include <stdio.h>
#include <pthread.h>

void *busy(void *ptr) {
// ptr will point to "Hi"
    puts("Hello World");
    return NULL;
}
int main() {
    pthread_t id;
    pthread_create(&id, NULL, busy, "Hi");
    while (1) {} // Loop forever
}
```

### 等待執行緒完成工作

如果要等待我們創建的執行緒完成工作，需要使用 `pthread_join` : 
```c=
int pthread_join(pthread_t thread, void **retval);
```
查看定義後，進一步改寫原本的程式碼:
```c=
#include <stdio.h>
#include <pthread.h>

void *busy(void *ptr) {
// ptr will point to "Hi"
    puts("Hello World");
    return NULL;
}
int main() {
    void *result;
    pthread_t id;
    pthread_create(&id, NULL, busy, "Hi");
    pthread_join(id, &result);
}
```

除了上面的範例，我們可以用 `pthread_exit()` 再做一次改寫:
```c=
#include <stdio.h>
#include <pthread.h>

void *busy(void *ptr) {
// ptr will point to "Hi"
    puts("Hello World");
    pthread_exit(NULL);
}
int main() {
    pthread_t id;
    pthread_create(&id, NULL, busy, "Hi");
    pthread_join(id, NULL);
}
```

若工作流程用圖表呈現，大概是這樣:

![pthread join](http://www.cs.fsu.edu/~baker/opsys/notes/graphics/forkjoin.gif)
> 上圖取自該[網站](https://www.cs.fsu.edu/~baker/opsys/notes/pthreads.html)。

### Compile your code!

本系列都是採用 gcc 作為 C 語言的編譯器，若使用到 Pthread 必須在編譯時添加參數: `-lpthread` 。
```
gcc source.c -lpthread -o source
```
編譯完成後，便可以啟動可執行檔。
```
./source
```

### 取消執行緒

PThread 提供了 API ，讓我們可以取消已建立的 POSIX Thread 。
```c=
int pthread_cancel(pthread_t thread);
```
> 想知道更多細節可以參考[該連結](https://blog.csdn.net/gswjj1/article/details/23302919)。

### exit 和 pthread_exit 的差異

`pthread_exit()` 如果放在 `main()` 函式，是用來確保所有用 POSIX Thread API 創建的執行緒已經完成。
```c=
int main() {
  pthread_t tid1, tid2;
  pthread_create(&tid1, NULL, myfunc, "Jabberwocky");
  pthread_create(&tid2, NULL, myfunc, "Vorpel");
  pthread_exit(NULL); 

  // No code is run after pthread_exit
  // However process will continue to exist until both threads have finished
}
```
如果不使用 `pthread_exit()` 或是 `pthread_join()` 而直接使用 `exit()` ，你的 Process 會在一派發完執行緒後結束(也就是執行緒根本還沒開始處理任務):
```c=
int main() {
  pthread_t tid1, tid2;
  pthread_create(&tid1, NULL, myfunc, "Jabberwocky");
  pthread_create(&tid2, NULL, myfunc, "Vorpel");
  exit(42); //or return 42;

  // No code is run after exit
}
```
> 如果還有疑問，也可以參考 [stackoverflow](https://stackoverflow.com/questions/3559463/is-it-ok-to-call-pthread-exit-from-main) 上的問答串。
## 總結
最後，筆者統整一下本篇介紹的 POSIX Thread API 的重要知識點: 
### 如何終止 Thread
終止 Thread 有 4 個方法:
- 等到 Thread 指派的任務 Return 。
- 用 `pthread_cancel` 呼叫指定的執行緒。
- 使用 `pthread_exit()` 。
- 終止 Process 。

### 如果不使用 `pthread_join` 會有什麼後果呢?
空閒的執行緒會繼續占用資源，直到 Process 結束為止。
換言之，如果是在長期不會結束的應用(像是伺服器)，那錯誤的設計便會造成多餘的資源浪費。

### 我該用 `pthread_join()` 還是 `pthread_exit()` 阿?
答案是都可以，只是差在 `pthread_exit()` 會在執行緒完成任務後退出，讓你沒有機會執行其他程式。

### 我可以在執行緒中傳送 Stack pointer 到另一個執行緒嗎?
可以，但要注意函式的生命週期，考慮以下程式碼:
```c=
pthread_t start_threads() {
  int start = 42;
  pthread_t tid;
  pthread_create(&tid, 0, myfunc, &start); // ERROR!
  return tid;
}
```
等到 `myfunc` 開始執行時， `start_threads()` 的生命早就走到盡頭了!這樣一來，我們根本無法確定原先存放 start 變數內容的記憶體現在存放什麼東西。
為了避免這個情況發生，我們可以用 `pthread_join` 改寫範例程式:
```c=
void start_threads() {
  int start = 42;
  void *result;
  pthread_t tid;
  pthread_create(&tid, 0, myfunc, &start); // OK - start will be valid!
  pthread_join(tid, &result);
}
```

這樣一來， `start_thread()` 的生命週期就會被延後到 `myfunc()` 執行完成才結束。

## Reference

- [C 語言 pthread 多執行緒平行化程式設計入門教學與範例](https://blog.gtwang.org/programming/pthread-multithreading-programming-in-c-tutorial/)
- [SystemProgramming](https://github.com/angrave/SystemProgramming/wiki/Pthreads%2C-Part-1%3A-Introduction)
- 維基百科