--- tags: linyunwen, raygoah --- # 2018q1 Homework (quiz13) ###### tags: `linyunwen` `raygoah` contributed by <`LinYunWen`, `raygoah`> - [第十三周練習題](https://hackmd.io/s/HJV0SEW1Q) ### 測驗 `1` 考慮以下 C 程式: ... 在 macOS 編譯並執行,得到以下程式輸出: ```= Main thread create threads Thread 1 create successfully thread1 : I'm thread 1 thread1 : number = 0 Thread 2 create successfully thread2 : I'm thread 2 thread2 : number = 0 pid-90400 Main thread wait for threads pid-0 Main thread wait for threads pid - 0 Thread 1 Finished pid - 0 Thread 2 Finished thread1 : number = 2 thread2 : number = 3 thread1 : number = 4 thread2 : number = 5 pid : 90400, thread1 : done pid - 90400 Thread 1 Finished pid : 90400, thread2 : done pid - 90400 Thread 2 Finished ``` 留意第 65 行 `pid = fork();` 敘述,`fork` 系統呼叫觸發後,會「分叉」執行單元為原本的 parent process 和新建立的 child process。從以下選項中挑出最符合的描述。 - 我們先看到 main function 裡面,先做了 mutex 的初始化,並且在 #67 呼叫 thread_create 來建立 threads ```clike=64 int main() { pthread_mutex_init(&mut, NULL); printf("Main thread create threads\n"); thread_create(); pid = fork(); printf("pid-%d Main thread wait for threads\n", pid); thread_wait(); return 0; } ``` - 而在建立的過程中,會去執行 thread1、thread2 function,這邊可以注意到的是,因為先建立 thread1,因此一定會先 print ```thread1 : I'm thread 1``` 再 print ```thread2 : I'm thread 2``` ```clike=42 temp = pthread_create(&thread[0], NULL, thread1, NULL) ``` ```clike=46 temp = pthread_create(&thread[1], NULL, thread2, NULL) ``` - 當 thread 建立時,他們個別會去呼叫 thread1, thread2,而這兩個 function ,幾乎是一樣,都是在一個 for loop 中,利用 mutex lock 對 number 這個變數做加一,然後 thread1 停 2s , thread2 停 3s ```clike=12 static void *thread1() { printf("thread1 : I'm thread 1\n"); for (i = 0; i < MAX; i++) { printf("thread1 : number = %d\n", number); pthread_mutex_lock(&mut); number++; pthread_mutex_unlock(&mut); sleep(2); } printf("pid : %d, thread1 : done\n", pid); pthread_exit(NULL); } ``` - 最重要的地方在於 #10 ,變數 number 和 i 都是全域的變數,而這個 number 是 for loop 中要輸出的變數, i 是 for loop 中的計數變數 ```clike=10 static int number = 0, i; ``` - 首先說明當 i < MAX 時,(MAX = 5),for loop 會繼續做下去,所以最重點是,你可以看到 thread1, thread2 是交互輸出,並且基本上應該會輸出共 5 次,因為計數用的 i 是共用的,所以兩者的輸出總次數應該不超過 5 次。那為什麼輸出結果會有六次呢?這要回顧一開始的輸出 #5, #7,當 #5 完成時,thread1 會有兩種可能,正在執行 mutex 內,或是 做完 mutex 內 已進入 sleep(2),但是不管如何這時的 i = 0 ,結果 thread2 就要開始執行,因此他進入 for loop 時 i 也還是 0 ,才會有共輸出六次這樣的狀況 - 接下來,對於上面同樣程式碼,在同樣環境下,執行結果卻不同的情況,要對 #7 thread2 的輸出做一下說明,剛剛提到過,在開始執行 thread2 時,可能遇到兩種狀況,thread1 正在執行 mutex 內的部份,或是做完 mutex 內的部份了,已經進入 sleep(2)。而這兩者的差別在於,因為 number 是全域變數,因此如果是狀況一,此時的 number = 0,因此 thread2 輸出 ```thread2 : number = 0```,反之,若是狀況二, number 已經加一,故輸出 ```thread2 : number = 1``` ```mermaid graph TB subgraph main Start --- create create --- fork join --- end_main(End) fork --- join end create --> do1(do loop) subgraph thread1 do1 --> join end create --> do2(do loop) subgraph thread2 do2 --> join end subgraph fork fork --> do3(do other) do3 --> end_fork(End) end ``` - 接著在 ubuntu 16.04 測試看看 * 在 ubuntu 16.04 的環境下編譯: ```kernel $ gcc -pthread -o 13 13.c ``` * 在 ubuntu 16.04 的環境下執行 * 第一次得到的結果如下: ```= Main thread create threads Thread 1 create successfully thread1 : I'm thread 1 Thread 2 create successfully thread1 : number = 0 thread2 : I'm thread 2 thread2 : number = 1 pid-4888 Main thread wait for threads pid-0 Main thread wait for threads pid - 0 Thread 1 Finished pid - 0 Thread 2 Finished thread1 : number = 2 thread2 : number = 3 thread1 : number = 4 thread2 : number = 5 pid : 4888, thread1 : done pid - 4888 Thread 1 Finished pid : 4888, thread2 : done pid - 4888 Thread 2 Finished ``` * 而多試幾次後,發現到,得到了和第一次執行結果不同的情形,如下: ```= Main thread create threads Thread 1 create successfully thread1 : I'm thread 1 thread2 : I'm thread 2 thread2 : number = 0 thread1 : number = 0 Thread 2 create successfully pid-16051 Main thread wait for threads pid-0 Main thread wait for threads pid - 0 Thread 1 Finished pid - 0 Thread 2 Finished thread1 : number = 2 thread2 : number = 3 thread1 : number = 4 thread2 : number = 5 pid : 16051, thread1 : done pid - 16051 Thread 1 Finished pid : 16051, thread2 : done pid - 16051 Thread 2 Finished ``` - 輸出結果不同主要是在 #5 到 #7 中 - 這時會看到,因為當 #5 完成時,thread1 會有兩種可能,正在執行 mutex 的部份,或是已經結束 mutex 的部份,進入到 sleep(2),而這兩種可能的差異,確實造成實際執行程式後得到的結果會有所不同 :::info 延伸題目: 1. 解釋程式運作,擴充測試的範圍 2. 參照 [Threads and fork(): think twice before mixing them](http://www.linuxprogrammingblog.com/threads-and-fork-think-twice-before-using-them),添增 `pthread_atfork()` 來改寫程式碼,使得 child/parent 兩個 process 的執行緒得以交替輸出 ::: ### 參考資料 * [Undefined reference to pthread_create in Linux](https://stackoverflow.com/questions/1662909/undefined-reference-to-pthread-create-in-linux)