<style> h2.part{color:#0099B0;} h3.part{color:#D92424;} h4.part{color:#005BB0;} h5.part{color:#FD6F0A;} h6.part{color:#4400B0;} </style> # 2016q3 Homework2 (phonebook-concurrent) contributed by <`f5120125`> ###### tags: `sysprog` ## 開發環境 #### Ubuntu 14.04 LTS - CPU: Intel® Core™ i5 CPU 650 @ 3.20GHz × 4 - Mem: 8 GiB - Cache: L1d cache: 32 KB L1i cache: 32 KB L2 cache: 256 KB L3 cache: 4096 KB ## Concurrency - ### Concurrency(並行性) 不為 Parallelism (平行性) [[1]](http://learn-gevent-socketio.readthedocs.io/en/latest/general_concepts.html)[[2]](https://hackmd.io/s/H10MXXoT)[[3]](http://stackoverflow.com/questions/1050222/concurrency-vs-parallelism-what-is-the-difference) - #### 實作threads主要是為了達到Concurrency: 再一條時間軸上, threads間有所重疊, 因此藉由共享==code section==, ==data section==, ==task上能夠運用的資源==達到有效利用CPU, 達到==vitual paralellism== - #### 實作processes主要是為了達到Parallelism: 再同一時間, 有多個tasks需處理,利用多個CPU去處理達到平行化 - ### [Sequenced Before](http://enginechang.logdown.com/posts/788206-concurrency-series-2-starting-from-sequenced-before) - threads間執行順序的不確定性, 得需知道求值順序 - ### [Happens-Before](http://enginechang.logdown.com/posts/797113-concurrency-series-3-happens-before) - 前一個操作的效果在後一個操作執行之前必須要visible - 只關注是否看起來有這樣的效果,從外界看起來,就像是先執行某一行A,再執行其接著的下一行B - 關鍵在於只要A的效果在B執行之前,對於B可見就可以了,實際上怎麼執行的並不需要深究 ## 案例分析 Phonebook-concurrent - 此程式延續之前作業, 修改為thread版本來提升效能 ### Preliminaries - ==**Macro**== 的使用 - **```#if...#else...#endif```** - **```#ifdef MACRO_NAME...#endif```, ```#ifndef MACRO_NAME...#endif```** - **```#if defined( MACRO_NAME )...#endif```** - **```#include IMPL```** - ==**Thread management**== - **```pthread_create()```** - **```pthread_join()```** - **```pthread_exit()```** - ==**Map file into memory**== - **```mmap()```** - **```munmap()```** - ==**File alignment**== ### 1. Macro 的使用 - 在```phonebook_opt.h```中定義了一個macro: ```OPT``` , 主要功能是拿來判斷```main.c``` 中哪裡是原來版本或是執行緒版本該執行的 #### example ```clike= #if OPT /*do sommthing*/ #endif ``` ```clike= #ifdef OPT /*do something*/ #endif ``` ```clike= #if defined(OPT) && OPT /*do something*/ #endif ``` ### 2. Thread management #### pthread_create() ```clike= int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg); ``` ###### ◆第一個參數 - ==**unique Thread ID**== ###### ◆第二個參數 - 用於指定不同 ==**thread的attributes**==, 通常為傳入NULL, 代表是default attribute ###### ◆第三個參數 - **```void *```** 為 ==**generic pointer**==, 主要功用 - ==**儲存任何型態的address**== - ==**typecast成任何型態**== ```clike= void *somePtr; int *integerPtr = 10; int *anotherIntPtr; somePtr = &integerPtr;/*儲存int型態的address*/ //error occur !! printf("%d\n", (int)(*somPtr));/*不能使用`*`去存取值*/ anotherIntPtr = (int*)somePtr;/*typecast成(int*)型態*/ printf("%d\n", *anotherIntPtr); ``` - **```(*start_routine)```** 為 ==**function pointer**==, 其所能傳入的參數型態為 **```void *```** ###### ◆第四個參數 - start_routine要用到的參數, 若是start_routine有許多參數, 可以傳入一個structure #### pthread_join() ```clike= int pthread_join(pthread_t thread, void **retval); ``` ###### ◆第一個參數 - ==**unique Thread ID**== ###### ◆第二個參數 - 用於 ##### pthread_exit() ### 3. Map file into memory #### MMAP [SYNOPSIS]((http://man7.org/linux/man-pages/man2/mmap.2.html)) ```clike= #include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); ``` mmap() 將檔案映射到一個 virtual memory address 上面,藉由直接對 memory address 修改的方式不用經過buffer,減少資料load到緩衝區的overhead,快速的記憶體存取可以提高對檔案 IO 的效率 :::info **Blocking** VS **Non-blocking I/O** * Blocking I/O 允許 sleep/awaken 動作的 process,基本動作: * 當 process 進行 read 動作但是資料尚未就緒時,process 必須被 block 住 (sleep),而當有資料進來時就需要立即被喚醒 (awaken),就算資料仍不完整也是 * 當 process 進行 write 動作但是 buffer 沒有空間時,process 也必須被 block 住,而且必須被放到跟 read 動作不同的 wait queue 中去等待 wake-up (buffer 騰出空間寫入) * Non-blocking I/O * 讀不到資料或寫不進 buffer 時,他會送出 retry 的動作,再試一次 >>[參考觀念](https://hackmd.io/AwJgzA7ARgHMCsBaEJjEQFgMYE4uJgFMcA2RXYEiYYmAMxgiA===?both) ::: >[記憶體映射函數 mmap 的使用方法](http://welkinchen.pixnet.net/blog/post/41312211) * `addr` : 要 map 到的記憶體位置 * 如果 addr 為 NULL,kernel 將在建立的 mapping 地址中自行選擇,這是建立新的 mapping 最簡單的方法。 * 如果 addr 不是NULL,mapping 會在附近的 page boundary 建立,並將新 mapping 地址傳回。 * `length`: 要 mapping 的長度,它會從 fd + offset 的位置開始 mapping 檔案。 * `prot`: 要保護的記憶體保護 mapping **不能跟 open mode 衝突** * PROT_EXEC 可能執行 * PROT_READ 可能讀取 * PROT_WRITE 可能寫入 * PROT_NONE 可能不能存取 * `flags`: 選擇 map file 能不能被其他程式看到 * MAP_SHARED 分享此 mapping * MAP_PRIVATE 建立一個私有 copy-on-write mapping * `fd`: 用系統函式 open 打開的檔案位置,open mode 可以選擇檔案的讀寫屬性,不能跟 prot 有衝突。 * `offset`: 從檔案的哪裡開始 mapping,offset 必須是 page size 的倍數,可以用 sysconf(_SC_PAGE_SIZE) 取得。 #### munmap munmap() 用來取消參數 start 所指的映射記憶體起始地址,參數 length 則是欲取消的記憶體大小。當行程結束或利用 exec 相關函數來執行其他程式時,映射記憶體會自動解除,但關閉對應的文件描述詞時不會解除映射。 >[C語言munmap()函數:解除内存映射](http://c.biancheng.net/cpp/html/139.html) ### 4. File alignment ### 5. 檢查aligned file是否和原輸入一樣 #### check.c ```clike= #include <stdio.h> #include <sys/stat.h>//stat, stat(paath, struct stat) #include <sys/mman.h>//mmap() #include <fcntl.h>//open(file_name, ) #include <stdbool.h> #define MAX 16 #define ORIG_FILE_PATH "./dictionary/words.txt" #define ALIGNMENT_FILE "align.txt" off_t getFileSize( char *path); int main(){ bool consistency=true; char orig_buff[MAX]; char align_buff[MAX]; FILE *orig_fp = fopen(ORIG_FILE_PATH, "r"); FILE *align_fp = fopen(ALIGNMENT_FILE, "r"); int fd = open(ALIGNMENT_FILE, O_RDONLY | O_NONBLOCK); off_t new_file_size = getFileSize(ALIGNMENT_FILE); char *map = mmap(NULL, new_file_size, PROT_READ, MAP_SHARED, fd, 0); char *tmp=map; while( fgets(orig_buff, sizeof(orig_buff), orig_fp) ){ if( strncmp(orig_buff, tmp, sizeof(orig_buff)) != 0){ consistency=false; break; } tmp+=MAX; } if(consistency) printf("Files are consistent\n"); else printf("Files are not the same\n"); return 0; } off_t getFileSize( char *path){ struct stat file_stat; stat(path, &file_stat); return file_stat.st_size; } ``` ##### 執行結果 - 檢查完aligned file和原本words.txt檔後, 確認檔案一樣 ``` Performance counter stats for './phonebook_opt' (100 runs): 841,854 cache-misses # 66.507 % of all cache refs ( +- 1.05% ) 1,265,807 cache-references ( +- 0.65% ) 172,850,372 instructions # 0.76 insns per cycle ( +- 0.07% ) 226,345,721 cycles ( +- 0.88% ) 0.090487839 seconds time elapsed ( +- 2.76% ) cc -Wall -std=gnu99 calculate.c -o calculate ./calculate hua@hua-ubuntu:~/Desktop/sysprog-class/homework2/phonebook-concurrent$ ./check Files are consistent ``` ### Code Refactoring - [x] 整理程式碼 - 變重新命名 - 整理Macro - [x] 程式的改進 - findName()的改進 #### original - 下列程式碼為append完後, 將threads間的linked-list合併在一起 - 改進方向可以考慮對每個thread做findName() ```clike= entry *etmp; pHead = pHead->pNext; for (int i = 0; i < THREAD_NUM; i++) { if (i == 0) { pHead = argsPtr[i]->entryHead->pNext; } else { etmp->pNext = argsPtr[i]->entryHead->pNext; } etmp = argsPtr[i]->entryTail; } ``` ##### 原本程式執行結果 ![image alt](http://i.imgur.com/ZQz4jds.png) #### 合併pthread_create()和pthread_join()的分析 ##### 執行結果 - create 和 join合併到同一個for loop ![image alt](http://i.imgur.com/yOwJiH0.png) #### threaded_findName() ##### 執行結果 ![image alt](http://i.imgur.com/pQxRqWf.png) ### Thread Pool >>[參考觀念](https://hackmd.io/GYVgLATAbGAmYFpYHYBGsFgMYEZgNRFgA4CBmVMYATmIENYAGLCIA===?both) - Thread 缺陷 - 建立太多執行緒,會浪費一定資源,並且有些執行緒未被充份使用 - 銷毀太多執行緒,會導致之後浪費時間再次創建它們 - 建立執行緒太慢,導致效能變差 - 銷毀執行緒太慢,導致其它執行緒沒有資源 - Thread Pool 功能 - 管控 Thread 的產生與回收 - 分發 Thread 處理 request - 處理 request 的 queue >[C 的 Thread Pool 筆記](http://swind.code-life.info/posts/c-thread-pool.html) >[github source code](https://github.com/mbrossard/threadpool) ```clike= typedef struct { void (*function)(void *); void *argument; } threadpool_task_t; //Thread Pool 需要 job/task 讓 Thread 知道他們要做什麼事情 threadpool_t *threadpool_create(int thread_count, int queue_size, int flags); //傳入 thread 的數量,queue 的大小,回傳 threadpool 型態是 threadpool_t int threadpool_add(threadpool_t *pool, void (*routine)(void *), void *arg, int flags); //傳入 threadpool,要執行的 function 位置,要傳入的參數 int threadpool_destroy(threadpool_t *pool, int flags); //傳入 threadpool,會把 threadpool 的空間 free 掉 ``` ### Lock-free Thread Pool #### Thread Pool VS Lock-Free Thread Pool * Thread Pool ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_VJmq0R0ILi6_p.537916_1459234875775_001.PNG) 共享 workqueue 的操作必須在 mutex 的保護下安全進行。 * Lock-Free Thread ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_VJmq0R0ILi6_p.537916_1459234893887_002.PNG) * 解決 lock 機制: 解決 lock-free 必須避免共享資源的競爭,因此將共享 workqueue 拆分成每個 worker thread 一個 workqueue。 對於 main thread 放入工作和 worker thread 取出任務的競爭問題,可以採取 ring-buffer 的方式避免。 * condition variable 問題: condition variable 解決執行緒之間的通訊。 semaphore 作為一種通信方式,可以代替之。 >[高效線程池之無鎖化實現](http://blog.csdn.net/xhjcehust/article/details/45844901) ```clike= void *tpool_init(int num_threads) //初始化時決定要用哪種方式進行 put work,選用 `ring-buffer` 的方式將 worker 加入queue static int wait_for_thread_registration(int num_expected) //確保所有執行緒都在準備中 int tpool_add_work(void *pool, void(*routine)(void *), void *arg) //配合 `dispatch` 加入 task 到 work queue 中 static void *tpool_thread(void *arg) //給 worker task,沒有用到 mutex_lock static tpool_work_t *get_work_concurrently(thread_t *thread) //利用 CAS 讓每個執行緒拿到自己的 task,確保從 work queue 出來時,遞減 `out` 的變數同步 void tpool_destroy(void *pool, int finish); //判斷所有的 queue 是不是空的,確保所有 worker 完成工作 ``` :::info **CAS** 以具有 atomic 特性的計算操作來達成。 ```clike= bool CAS(T* addr, T exp, T val) { if (*addr == exp) { *addr = val; return true; } return false; } ``` ::: >[github source code](https://github.com/xhjcehust/LFTPool) #### 實做測試 ```shell $ git clone https://github.com/xhjcehust/LFTPool $ cd LFTPool/ $ make $ ./testtpool ```