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); } ``` 可能的輸出: ![](https://i.imgur.com/Gqbq7Yj.png) ## 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)