# 2016q3 Homework2 (phonebook-concurrent) contributed by <`ChenYi`> ###### tags `ChenYi` `sysprog` ## 開發環境 Distribution: Ubuntu 16.04 LTS ``` Architecture: x86_64 CPU 作業模式: 32-bit, 64-bit Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 每核心執行緒數:2 每通訊端核心數:4 Socket(s): 1 NUMA 節點: 1 供應商識別號: GenuineIntel CPU 家族: 6 型號: 60 Model name: Intel(R) Xeon(R) CPU E3-1231 v3 @ 3.40GHz 製程: 3 CPU MHz: 3748.632 CPU max MHz: 3800.0000 CPU min MHz: 800.0000 BogoMIPS: 6784.07 虛擬: VT-x L1d 快取: 32K L1i 快取: 32K L2 快取: 256K L3 快取: 8192K ``` ## 預期目標 * 做好筆記 (上次的自己看都覺得慘不忍睹) >> 幻滅是成長的開始 [name=jserv] * 弄懂concurrency 和 parallelisms 的差別 * 學會使用pthread * 效能分析 之前對於這類東西一直覺得很混淆,學OS的時候也只是考過就忘,這次先嘗試把背景知識補齊再開始動工 >> 這部份一直很弱ˊ~ˋ [name=ChenYi] ## 教材閱讀重點整理 閱讀[冼鏡光的並行計算投影片](http://blog.dcview.com/article.php?a=BTgBYw1qUWIAaQ%3D%3D)與[Toward concurrency](https://hackmd.io/s/H10MXXoT)的一些重點紀錄 #### 一些要搞懂的關鍵字 * 行程(Process) * 一個process中可以有多個thread並行執行 * 不同的process有不同的記憶體空間 * 執行緒(Thread) * 在同個process內的執行緒可以同時共享資源(ex.記憶體,code * 在大多數的狀況下,process由threads所組成 * 若CPU有多個processor,可以做到平行化執行(parallel) * 並行(Concurrency) * 多個問題交由一個processor處理 * 可連續或交錯進行 * 平行(Parallelism) * 同個問題交由多個processor來處理 * 同時並進 * Asynchronous 和 Synchronous用圖解比較好懂 >> 可用 [GraphViz](http://www.graphviz.org/) 製圖,HackMD 有支援。中文示範: [Graphviz - 用指令來畫關係圖吧!](http://www.openfoundry.org/tw/foss-programs/8820-graphviz-) [name=jserv] * Synchronous * ![](https://i.imgur.com/G9cNDcd.png) * 左右分別是synchronous的1 thread || multi-threads * O1, O2 , O3皆為空白時間 * Asynchronous * ![](https://i.imgur.com/GvQJQpm.png) * Conditional Variable * 資料參考 https://www.cs.mtu.edu/~shene/NSF-3/e-Book/MONITOR/CV.html * conditional variable事實上是event而不是variable * conditional varialbe有wait和signal的operation * 當一個thread等待有事件發生時,他放入waiting queue中(等待呼叫 ==> wait * 當該thread被呼叫後,會執行signal * signal發生時,monitor會尋找在waiting queue中的其中一個thread來執行 * 若完全沒有的話,這個signal就會被完全忽視掉 * Semaphore(信號標) * 是一個具有counter , waiting list ,和兩個作用signal和wait * counter主要有兩種 * counting * binary * 和conditional variable的差別在conditional variable的signal若沒有人接收是會忽視的,而semaphore若沒有設立處理函式的話,有可能導致thread或是整個程式的中止 * 所以必須在thread pool產生執行緒前就事先指定處理函式。 在[The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software](http://www.gotw.ca/publications/concurrency-ddj.html)一文中指出由於硬體製作商,在時脈,散熱,與功耗上遇到了瓶頸(提高時脈,提高溫度,但散熱能力仍無法有效改善)又因為64bits系統所用的程式基本型態例如pointer size從 4 -> 8 bytes,而導致cache裡能存的資料更少,所以逐漸轉向multi-processor的製作,及加大CPU內部的cache size。撰寫Concurrency與Parallel的程式變成了一種趨勢,而不是單純地靠著硬體設備上的更新,就能使程式跑的更快更有效率。 ##### Functional Programming正好是軟體開發逐漸走向multi-thread的受益者 * 因為Funcitonal Languages不會共享資源(有自己的區塊) * 不需要設鎖就可以確保程式的正確性 * 愈來愈多的程式語言支援部份的functional programming >> 不要打錯字,是 "functional (programming) language",知道重要性後,趕快把 recursion 學好,頭腦體操可以磨練你。 [name=jserv] >> 謝謝提醒 [name=chenyi] #### Thread pool * 紀錄有多少thread可以使用,且有多少工作要做 * 讓各執行緒知道該做什麼工作 於 [Toward Concurrency](https://hackmd.io/EYRgzAbAZgDA7ADgLRQJwIMZICww2JYCCLOAQ2GAQQFMxVUATVIA?view#lock-free-thread-pool) 裡有比較詳細的實作,裏面使用的方法是mutex lock,但mutex的獲取與釋放,對於效能會有一定的影響 所以後面又提出了lock-free的解決方案 >> 問題不在於 branch,而在於同步的成本,你之前參照的文章就提到了。 [name=jserv] >> 喔喔 感謝提醒! [name=chenyi] #### Lock-Free Thread Pool ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_VJmq0R0ILi6_p.537916_1459234875775_001.PNG) 上面是一般會使用的thread pool實作方式 以下是lock-free化的thread pool ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_VJmq0R0ILi6_p.537916_1459234893887_002.PNG) 一般的thread pool實作方式是使用共用的work queue,以mutex保護,在發現待執行工作數目大於worker thread的情況下,將會利用conditional variable來喚醒等待狀態的worker thread。 不同於一般的作法,lock-free的作法是給予每個worker threads自己的work queue。 對於main-thread放工作和worker thread取工作的衝突採ring-buffer解決 此實作參考 https://github.com/xhjcehust/LFTPool * Ring buffer ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_VJmq0R0ILi6_p.537916_1459234911980_003.PNG) 在這個實作裡的任務排程為 * Round-Robin : 一個一個問有沒有空來工作 * Least-Load : 找最閒的那位 * 另外還有做Task migration * 考慮各個worker的load-balancing的話,task的遷移是必須的 * 有兩個function * migrate in - 由main thread分配工作 * migrate out - 存在競爭資源問題 (該worker可能正在做事 * 利用C11裏面就有的atomic機制來處理lock問題 * atomic為同步處理機制,不用像是mutex的東西,便能確保變數間的同步 ## POSIX Threads 資料來自於大家所熟知的 [POSIX Threads Programming](https://computing.llnl.gov/tutorials/pthreads/)上 >> 看了一下這邊好像沒什麼能寫的...上面的教學網站太好用了,man上也幾乎都能解決,暫時不做這邊的筆記[name=chenyi] [Mutexes](https://computing.llnl.gov/tutorials/pthreads/#Mutexes) 試著從他的mutex範例學習(只紀錄重點) (程式碼的部份連上去比較好看清楚mutex相關的部份) ```C pthread_mutex_lock (&mutexsum); dotstr.sum += mysum; pthread_mutex_unlock (&mutexsum); ``` (mutex作用為提供各個執行緒在寫入共享記憶體區段的時候,避免覆(誤)寫的情況出現) 所以在進critical section的時候,要使用pthread_mutex_lock()將寫入鎖住,讓其他執行緒,等待該執行緒寫入完成。 ## 實作部份 記一下要做什麼 * 預計實作一般版的thread pool * 效能測試及畫圖 修正一下main.c的部份 ```C= entry *etmp; pHead = pHead->pNext; for (int i = 0; i < THREAD_NUM; i++) { if (i == 0) { pHead = app[i]->pHead->pNext; dprintf("Connect %d head string %s %p\n", i, app[i]->pHead->pNext->lastName, app[i]->ptr); } else { etmp->pNext = app[i]->pHead->pNext; dprintf("Connect %d head string %s %p\n", i, app[i]->pHead->pNext->lastName, app[i]->ptr); } etmp = app[i]->pLast; dprintf("Connect %d tail string %s %p\n", i, app[i]->pLast->lastName, app[i]->ptr); dprintf("round %d\n", i); } ``` 這邊的pHead = pHead->next的部份是沒意義的(pHead->next在此時為NULL) 然後進去迴圈時的i=0的時候,可以省去做判斷,提出來在外面,大部份for迴圈裏面的都是debug用的樣子,另外沒理解錯的話,i = 0的時候phead的值應該要是 app[0]->pHead 才對,etmp目的應該是為了串接分開來的app,所以回圈裡的etmp->pNext應該要給予的值是app[i]->pHead 刪減過後 ```C= entry *etmp; pHead = app[0]->pHead; etmp = app[0]->pLast; for (int i = 1; i < THREAD_NUM; i++) { etmp->pNext = app[i]->pHead; etmp = app[i]->pLast; } ``` 重整後在試跑一次確認程式正確性 ``` ./phonebook_opt size of entry : 24 bytes execution time of append() : 0.005126 sec execution time of findName() : 0.005588 sec ``` 修正後各thread執行時間 ![](https://i.imgur.com/v1dIqJt.png) 我認為會慢因為原本的方法裏面採的有順序的join (0-1-2-3) ```C for (int i = 0; i < THREAD_NUM; i++) pthread_join(tid[i], NULL); ``` 以上面的4條thread為例,若是2先跑完,他必須等到0和1跑完join完才能join進main thread裡 而當thread數目夠高,各thread完成的順序就不會是0-1-2-3-4....而會是看系統怎麼安排(或者原本有寫thread pool去作管控) 先試試看[吳彥寬的實驗筆記](https://hackmd.io/s/BkN1JNQp)裡提到的用非同步的join > 觀察一下程式碼,可以發現是在各自thread裡做append的動作,最後才做串接,也就是說,整個append的過程應該是沒有critical section的,如果能各自先結束並儲存,不要等待其他thread完成的話,應該能節省不少時間 [name=`chenyi`] > ## Reference * [作業網站](https://hackmd.io/s/rJsgh0na) * [Toward Concurrency](https://hackmd.io/EYRgzAbAZgDA7ADgLRQJwIMZICww2JYCCLOAQ2GAQQFMxVUATVIA?view) * [Process wiki](https://en.wikipedia.org/wiki/Process_(computing)) * [Thread wiki](https://en.wikipedia.org/wiki/Thread_(computing)) * [Asynchronous vs synchronous execution, what does it really mean?](http://stackoverflow.com/questions/748175/asynchronous-vs-synchronous-execution-what-does-it-really-mean) * [POSIX Thread Programming](https://computing.llnl.gov/tutorials/pthreads) * [C 的Thread pool筆記](http://swind.code-life.info/posts/c-thread-pool.html) * [Semaphore wiki](https://en.wikipedia.org/wiki/Semaphore_(programming))