# 2016q3 Homework3 (mergesort-concurrent) contributed <`shelly4132`> ## 開發環境 * Ubuntu 16.04 LTS ``` $ lscpu ``` * Architecture: x86_64 * CPU op-mode(s): 32-bit, 64-bit * Byte Order: Little Endian * CPU(s): 4 * Model name: Intel(R) Core(TM) i5-4200U CPU @ 1.60GHz * L1d cache: 32K * L1i cache: 32K * L2 cache: 256K * L3 cache: 3072K ## 執行看看 * [src](https://github.com/sysprog21/mergesort-concurrent) ```shell $ make $ ./sort 4 8 input unsorted data line-by-line 5 2 11 65 1 9 30 0 sorted results: [0] [1] [2] [5] [9] [11] [30] [65] ``` 後面兩個參數分別填上thread的數量和總共要排序多少數字,輸入完後再將要排序的數字一行一行打上去,最後就會顯示排序好的數列。 如果不想自己手動打的話也可以利用```$RANDOM```環境變數取得介於 0~32767 之間的亂數。 ```shell $ (for i in {1..8}; do echo $RANDOM; done) | ./sort 4 8 input unsorted data line-by-line sorted results: [1623] [2036] [3472] [3809] [11126] [23029] [28806] [30389] ``` ## 理解code * task:讓thread知道自己要做什麼 ```clike= typedef struct _task { void (*func)(void *); void *arg; struct _task *next, *last; } task_t; ``` * queue:裡面放置著等待要被處理的task們 ```clike= typedef struct { task_t *head, *tail; pthread_mutex_t mutex; pthread_cond_t cond; uint32_t size; } tqueue_t; ``` * thread pool:裡面放置了所有thread和job queue ```clike= typedef struct { pthread_t *threads; uint32_t count; tqueue_t *queue; } tpool_t; ``` 指派第一個task並將之放進job queue裡,`the_list`是前面建好的使用者輸入數列的linked list,而`cut_func()`就是用來切割數列的。 ```clike= task_t *_task = (task_t *) malloc(sizeof(task_t)); _task->func = cut_func; _task->arg = the_list; tqueue_push(pool->queue, _task); ``` * intptr_t: 可pointer轉換成integer型態,能做位元運算與加減法等等。但如果要做位元運算的話無號的`uintptr_t`會是比較好的選擇。 * `merge_list()`傳入兩個list,然後去比較2個list最小的那個再一一挑出,放到新的list裡面,直到其中一個list的數被挑完,還有數字的list就接到已排序好的list後面。 * `merge_sort()`如果傳入的list size小於2直接回傳該list,否則就將他從中間分成2個list之後丟進`merge_list()`做排序並回傳排序好的list。 * `merge`裡面去判斷現在的list總共串了幾個,如果小於輸入的資料數,就繼續做排序串起來,不然就設置終止task然後印出排序好的list。 發現cut_func()跟merge_sort()都是再做切割list,而cut_func裡面判斷cut_count是否小於max_cut其實沒有必要,所以把cut_count等相關變數刪掉,也把merge_sort()刪掉,留下cut_func()就好。 ### [manager-worker 架構](http://maxim.int.ru/bookshelf/PthreadsProgram/htm/r_19.html) 由一個boss thread去接收input並分派task給其他worker thread。 ```graphviz digraph hierarchy { nodesep=0.5 // increases the separation between nodes node [color=black,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=black, style=dashed] // All the lines look like this Manager->{Worker1 Worker2 Worker3} } ``` 將我們要做的事寫成task,放進job queue裡,空閒的thread會從queue裡拿task出來做。 ```graphviz digraph { 開始Process[shape="box", style=rounded]; 是否為結束Task[shape="diamond"]; 結束Process[shape="box", style=rounded]; 開始Process->提取Task->是否為結束Task 是否為結束Task->重發結束Task[label="是"] 重發結束Task->結束Process 是否為結束Task->執行Task[label="否"] 執行Task->提取Task; } ``` 去queue裡面提取task。 ```clike= static void *task_run(void *data) { (void) data; while (1) { task_t *_task = tqueue_pop(pool->queue); if (_task) { if (!_task->func) { tqueue_push(pool->queue, _task); break; } else { _task->func(_task->arg); free(_task); } } } pthread_exit(NULL); } ``` ### Verify code 新增一個`test_input.c`會產生亂數於`test_input.txt`裡,原本的程式去讀取他後去做merge sort將結果輸出於`output.txt`中,最後再將`output.txt`與由`output.txt`sort完後的`sorted.txt`去做比較,如果結果都一樣就輸出「Verified OK」。 ### 將輸入改為phonebook的字典檔 * 將字定義的val_t的型態改為字元陣列 ```clike typedef char val_t[16]; ``` * 使用`uniq words.txt | sort -R > words_input.txt`讓原本排序好的words.txt順序打亂,變成merge sort新的輸入。 * 在`merge_list()`裡的比大小改為用`strcmp(a->head->data, b->head->data)`的回傳值去做判斷,若前者較大回傳值大於0,等於回傳0,否則回傳負值。 #### 建立checker.c去判斷執行結果對不對 ```clike= while (fgets(answer, sizeof(answer), fp1) != NULL) { fgets(output, sizeof(output), fp2); if (strcmp(answer, output) != 0) { printf("Wrong Answer!\n"); return 0; } } printf("Verified OK\n"); ``` * 不同thread數目執行時間 ![](https://i.imgur.com/9Pkup6c.png) ## Lock Free Thread Pool ## 參考 * [A07: mergesort-concurrent](https://hackmd.io/EwEwHAxgrGCcBmBaAzPMAjRAWAphxAhlhJslLAOwBsqUIy4QA===?view) * [What is the use of intptr_t?](http://stackoverflow.com/questions/35071200/what-is-the-use-of-intptr-t) * [TempoJiJi](https://github.com/TempoJiJi/mergesort-concurrent) * [andy19950](https://github.com/andy19950/mergesort-concurrent)