Pthread tutorial
======
###### tags: `C` `OS`
# 前言
POSIX thread(Pthread) library是一個由POSIX標準制定的thread library,可以使用C/C++來做API的呼叫。但要注意的是,Windows OS API (Win32)裡面是沒有包含這個library,所以我們並不能直接在Windows環境下使用此library
當然<windows.h>裡面有他們自己實作的類似功能。不過此篇tutorial會把重心放在Pthread library上,若是要在Windows上使用pthread,可以自尋搜尋第三方re-implement的pthread library for Windows。或者使用類似[Repl.it](https://repl.it/languages/c)這種的線上C編輯器來做下面的練習。
此篇tutorial主要是基於參考來源來進行搬運,內容僅有些許微調。若對原文有興趣請至參考來源。
# Thread基礎
* Thread操作包括了:
* thread creation
* thread termination
* thread synchronization (joing, blocking)
* thread scheduling
* thread data management
* thread process interaction
* 一個thread不會去維護紀錄已創建threads的list,它也不會知道是哪個執行序創造自己。
* 在同一process中的所有threads都共享同一塊memory address space。
* 在同一process的thread共享:
* Process instructions
* Most data
* Open files (descriptors)
* Signals and signal handlers
* Current working directory
* User and group id
* 每個thread都有自己特有的:
* Thread ID
* set of registers與stack pointer
* stack for local variables, return addresses
* signal mask
* priority
* Return value: errno
* pthread函式若回傳0則代表正常
# Thread創建與結束
```clike=
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void *print_message_function(void *ptr);
int main(int argc, char** argv)
{
pthread_t thread1, thread2;
const char *message1 = "Thread 1";
const char *message2 = "Thread 2";
/* 使用pthread_create創建pthread,回傳值若為0代表成功建立thread。 */
/* thread1會被賦予thread_id */
/* 並執行function print_message_function,夾帶參數message1 */
int iret1 = pthread_create(&thread1, NULL, print_message_function, (void*)message1);
if(iret1 != 0){
fprintf(stderr, "pthread_create thread failed!");
return 0;
}
int iret2 = pthread_create(&thread2, NULL, print_message_function, (void*)message2);
if(iret1 != 0){
fprintf(stderr, "pthread_create thread failed!");
return 0;
}
/* 要先等待thread創建完畢再讓main繼續執行*/
/* 否則有可能在thread創建完之前,main就跑到exit結束此process*/
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("Thread 1 returns: %d\n", iret1);
printf("Thread 2 returns: %d\n", iret2);
exit(0);
}
void *print_message_function(void *ptr)
{
const char *message;
message = (const char*)ptr;
printf("%s\n", message);
}
```
可能的Output(message順序不一定,端看哪個thread創建完畢且先執行print_message_function)
```
Thread 2
Thread 1
Thread 1 returns: 0
Thread 2 returns: 0
```
# Thread同步(Synchronization)
Pthread library提供三種同步機制:
1. mutexes
互斥鎖 (Mutual exclusion lock) 可以阻擋一個鎖住 (lock) 的變數被其他thread存取,該變數在被互斥鎖解鎖 (unlock) 之前都只有將其上鎖的thread能夠使用。
2. joins
讓一個thread等到其他threads都結束 (terminated) 之後才執行。
3. condition variables
資料型態為<code>pthread_cont_t</code>的變數,可幫助我們控制同步時機。
## Mutexes
Mutexes可用來保護data免於競爭狀態 (race condition) 而造成的inconsistencies。而競爭狀態好發於當有兩個以上threads對同一塊記憶體區域做操作,這種情況發生時,執行結果會取於他們的執行順序不同而有所改變,對於開發者來說可能會造成難以察覺的Bug。
Mutexes可以用來保護共享資源 (shared resources)。任何全域資源會被超過一個以上的thread使用時,都應該要用一個Mutex去保護它。但要注意的是,**Mutex並不像semaphores一樣能夠保護其他process的資源,Mutex的作用域僅限於同一process之內的所有threads。**
```clike=
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void *incrementCounter();
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;
int main(int argc, char** argv)
{
int rc1, rc2;
pthread_t thread1, thread2;
if((rc1 = pthread_create(&thread1, NULL, (void*)&incrementCounter, NULL))){
printf("Thread creation failed: %d\n", rc1);
}
if((rc2 = pthread_create(&thread2, NULL, (void*)&incrementCounter, NULL))){
printf("Thread creation failed: %d\n", rc2);
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
exit(0);
}
void *incrementCounter()
{
pthread_mutex_lock(&mutex);
counter++;
printf("Counter value: %d\n", counter);
pthread_mutex_unlock(&mutex);
}
```
輸出將會是:
```
Counter value: 1
Counter value: 2
```
## Joins
join是用來等待一個其他特定thread跑完時再繼續往下執行的情境使用。當所等待的thread已經terminated時,<code>pthread_join()</code>就會馬上return。如果return value不是NULL,則會copy該thread的exit status。
```clike=
#include <stdio.h>
#include <pthread.h>
#define N_THREADS 10
void *thread_func(void*);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;
int main(int argc, char** argv)
{
pthread_t thread_id[N_THREADS];
int i, j;
for(i = 0 ; i < N_THREADS ; i++)
pthread_create(&thread_id[i], NULL, (void*)&thread_func, NULL);
for(j = 0 ; j < N_THREADS ; j++)
pthread_join(thread_id[j], NULL);
printf("Final counter value: %d\n", counter);
return 0;
}
void *thread_func(void* dummyPtr)
{
// 用來避開某些編譯器會報Warning(未使用的argument)
(void)dummyPtr;
printf("Thread number %ld\n", pthread_self());
pthread_mutex_lock(&mutex);
counter++;
pthread_mutex_unlock(&mutex);
}
```
可能的輸出:

## Condition Variables
條件變量 (condition variable) 是一種機制,它能夠允許thread暫時suspend執行並放棄搶CPU資源直到條件變量為True時。**條件變量一定要搭配mutex使用以避免掉race condition的情境**。比如說thread 1準備等待,而另一個thread 2可能會在thread 1實際開始等待前便先signal了條件變量,這將導致死結的發生(互相等待,永不開始),因為thread 1會一直等待不會被送來的signal。
* Creating / Destroying
* pthread_cond_init
* pthread_cond_t cond = PTHREAD_COND_INITIALIZER
* pthread_cond_destroy
* Waiting on condition
* pthread_cond_wait
* pthread_cond_timewait (設置它會block多久)
* Waking thread based on condition:
* pthread_cond_signal
* pthread_cond_broadcast (喚醒所有被特定條件變量block住的threads)
```clike=
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define COUNT_DONE 10
#define COUNT_HALT1 3
#define COUNT_HALT2 6
pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t condition_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t condition_cond = PTHREAD_COND_INITIALIZER;
void *func1();
void *func2();
int count = 0;
int main(int argc, char** argv)
{
pthread_t thread1, thread2;
int iret1, iret2;
if((iret1 = pthread_create(&thread1, NULL, (void*)&func1, NULL))){
printf("create thread 1 failed!");
return 0;
}
if((iret2 = pthread_create(&thread2, NULL, (void*)&func2, NULL))){
printf("create thread 2 failed!");
return 0;
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
return 0;
}
void *func1()
{
for(;;)
{
pthread_mutex_lock(&condition_mutex);
while(count >= COUNT_HALT1 && count <= COUNT_HALT2)
pthread_cond_wait(&condition_cond, &condition_mutex);
pthread_mutex_unlock(&condition_mutex);
pthread_mutex_lock(&count_mutex);
count++;
printf("Counter value func1: %d\n", count);
pthread_mutex_unlock(&count_mutex);
if(count >= COUNT_DONE)
return NULL;
}
}
void *func2()
{
for(;;)
{
pthread_mutex_lock(&condition_mutex);
if(count < COUNT_HALT1 || count > COUNT_HALT2)
pthread_cond_signal(&condition_cond);
pthread_mutex_unlock(&condition_mutex);
pthread_mutex_lock(&count_mutex);
count++;
printf("Counter value func2: %d\n", count);
pthread_mutex_unlock(&count_mutex);
if(count >= COUNT_DONE)
return NULL;
}
}
```
輸出為:
```
Counter value functionCount1: 1
Counter value functionCount1: 2
Counter value functionCount1: 3
Counter value functionCount2: 4
Counter value functionCount2: 5
Counter value functionCount2: 6
Counter value functionCount2: 7
Counter value functionCount1: 8
Counter value functionCount1: 9
Counter value functionCount1: 10
Counter value functionCount2: 11
```
<code>func1()</code>會在<code>count</code>值介於<code>COUNT_HALT1</code>與<code>COUNT_HALT2</code>時停止。唯一能確定的只有<code>func2</code>會在值<code>COUNT_HALT1</code>與<code>COUNT_HALT2</code>之間遞增。剩下其他的都是隨機。
邏輯條件(<code>if</code>與<code>while</code>敘述)必須被選擇來保險說每當"wait"被processed時,"signal"會被執行。
* Note
此範例仍會產生race condition,因為count被當作condition且不能被鎖在不會造成deadlock的while敘述裡。
# Thread Scheduling
當此選項被啟用時,每個thread都會有自己的排程屬性。排程屬性可以被特定為:
* 當thread創造時
* 動態改變現存的thread attributes
* 當創造一個mutex時,定義這個mutex對於thread排程的效果
* 在同步操作時,動態改變一個thread的排程
# Thread Pitfalls
* Race condition
thread的執行順序由OS安排,執行速度很可能不相同,因此造成不預期的結果。
* Thread safe code
所有的thread routine一定只能執行"Thread safe"的function。這意味著不能在thread中執行任何有可能會跟其他thread相衝的靜態或者全域變數,若有使用需求則一定要搭配mutex使用或者重寫function。
* Deadlock
死結好發於當使用mutex但又沒有解鎖的情況,這會導致程序永遠被blocked住。
# 參考來源
[POSIX thread (pthread) libraries](https://www.cs.cmu.edu/afs/cs/academic/class/15492-f07/www/pthreads.html)
[pthread_join(3) - Linux manual page](http://man7.org/linux/man-pages/man3/pthread_join.3.html)