Try   HackMD

2016q3 Homework2 (phonebook-concurrent)

contribute by <yenWu>

refactor

看完兩個中文連結,就我理解上,所謂的 refactor的目的就是讓程式更易於維護、新增功能、容易找出bug出在那邊,將程式的分工並將功能最小化增加 reuse,而他是一個在寫程式時就該思考該改善的步驟,如和將程是的分工變得非常明確,不讓重複的code一直重寫。

Refactoring》書中提到22個壞味道,包含:duplicated code、long method、large class、long parameter list、divergent change、shotgun surgery、feature envy、data clumps、primitive obsession、switch statements、parallel inheritance hierarchies、lazy class、speculative generality、temporary field、message chains、middle man、inappropriate intimacy、alternative classes with different interfaces、incomplete library class、data class、refused bequest、comments。

參考:對於重構的兩則常見誤解

檢討自己的缺失

  1. 變數命名很爛

解決方式:這個部份其實我參考了很多命名的文件,像是 Camel case 的方式(ex: myName),或是匈牙利命名法,我最終選擇 Camel case(可是老師給我的 comment是 linux kernel 不使用這些),目前我已經有定立自己的 coding style。

  1. 沒有註解,程式碼流程非常的混雜,讓人不清楚這一大串到底再幹嘛。

解決方式:在每一個流程前給一個註解,應該說在寫 code 時就應該要建立這樣子的流程圖,而不是寫完之後再去加上。

  1. 可以省略的變數有點多,有些根本是不必要的。

解決方式:就以 append()這裏麵的很多變數可以不用的,像是 entryStart,搞不好連 nthread也可以省略,因為有 Macro 在。

  1. main 裡面的 code太多了,會有 duplicated code, long method的問題。

解決方式:說到 code reuse,其時我應該把大部分的 code 丟到phonebook opt裡,這樣會讓 main看起來簡單易懂,而且也能增進 reuse。

  1. 在 make 時定義 THREAD NUM,這非常的麻煩

解決方式: Get number of CPUs in Linux using C jserv
水啦,這個好神啊Yen-Kuan Wu

回傳系統資訊 in C

sysconf(3) 主要回傳數字,透過不同的 flag 能得到不同的回傳值,算是挺全面的。

我用它來改我的 hardcode THREAD_NUM
#define THREAD_NUM (int)(sysconf(_SC_NPROCESS_ONLN))

我也有練習其他的部分,這邊就不列舉了。
https://github.com/yenWu/PracticeMakePerfect/commit/6e3ae2b25a75c6ad11d556838c4a75e7ab738b87

上面的版本會有個問題是,重複 call這個 function,所以最終版本是這個

const int thread_num = (int)sysconf(_SC_NPROCESSORS_ONLN); #define THREAD_NUM thread_num

補齊document 和 新增 Makefile 的 TIMING

將計算細項時間,替換到 TIMING
make TIMING=1

phonebook物件化

說到 refactor的改善,我嘗試想把我的 code做物件化,我覺得他給我們最大的好處就是,讓別人一眼就看出來我們的 phonebook的 attribute 和 可以使用的 method,而不會是像 append這樣簡易的命名,讓我們不清楚他到底再 append哪些東西,而且要加額外的功能只要再多開一個 function pointer,大家一目了然phonebook 多了什麼功能。

  • phonebook
typedef struct _phonebook { entry *pHead; entry *pLast; unsigned long long count; entry *(*append)(char *lastName, entry *pHead); entry *(*find)(char *lastName, entry *pHead); } phonebook;

請留意縮排風格,用 4 個空白而非 tab。我們期望寫作 entry *pHead 而非 entry* pHead,operator * 放的位置很重要 jserv
好的,這我真的沒注意到,囧。Yen-Kuan

這個部份我會留在後面再改動,這其時會改到全部程式碼,因為我希望把 list都保護在phonebook裡。

想到 code reuse,我目前在做一份比較完整版的 linkList,考慮到了如果我們今天突然 linkList裡的變數不在是 int data的話,我要怎樣改動最少的 code呢?

code: W2

typedef struct _context{ int data; } context; typedef context *ctxPtr; typedef struct _listNode { ctxPtr ctx; struct _listNode *next; } listNode;

將 new 跟 init 分離。

static ctxPtr initCtx() { ctxPtr p = (ctxPtr) malloc(sizeof( context));///FIXME:Dup assert(p && "malloc error"); return p; } static ctxPtr newCtx(int data) { ctxPtr p = initCtx(); p->data = data; return p; }

這邊我又想到了,這個 malloc也是一直一直再寫,assert也要一直寫,所以我有沒有辦法把他包成一個 function呢?這的感覺就像是在寫 system 時要常常注意 open有沒有開成功,一堆的。

回到原點,我該怎樣讓一個 malloc 支援很多 type勒, template

Template in C

這邊我想了一陣子,想到最初的版本就是 Macro,用他來替換字元是最好的方式

#define malloc(type, ctxtype, num) \ type malloc(sizeof(ctxtype) * num)

但後來覺得 Macro是直接替換,所以真的比較難 debug,所以我詢問老師後,老師建議我使用 C11 的 _Generic,大概用法如下,我也寫過一個範例如下,這感覺出乎意料的好用

#define printf_dec_format(x) _Generic((x), \ char: "%c", \ signed char: "%hhd", \ unsigned char: "%hhu", \ signed short: "%hd", \ unsigned short: "%hd", \ signed int: "%d", \ unsigned int: "%u", \ unsigned long int: "%ld", \ long long int: "%lld", \ unsigned long long int: "%llu", \ float: "%f", \ double: "%lf", \ long double: "%Lf", \ char *: "%s", \ void *: "%p")
#define print(x) printf( printf_dec_format(x), x) #define printnl(x) printf( printf_dec_format(x), x); printf("\n") int main(int argc, char *argv) { printnl('a'); printnl((char) 'a'); printnl(123); printnl(1.234); }

請修正 coding style! 不要亂加空白,「( 之後」和「) 之前」都不該有額外的空白。重申: 4 個 space,而非 tab,程式開發很在意紀律和規範。 jserv
慘了= =,我可以請問老師一下是直接敲4個space 還是說astyle 或是改 vimrc。Yen-Kuan Wu
直接在 .vimrc 改比較方便,把 tab 轉換成 spaces。
set expandtab " 空格取代 tab
set tabstop=4 " 4 個空格
set shiftwidth=4 " 針對縮排所需要的空格個數 Charles Lee
Thanks. 我也是這麼想的,感謝分享,我這就去改.vimrcYen-Kuan Wu

thread pool

參考 Mathias Brossard 的 mbrossard/threadpool (BSD Licens)

他這份實作有完整程式碼,行數不是很多,但他開出來的 api跟 struct member都非常清楚,不像我的 append_a完全不知道是什麼。

這邊我看到他的 thread 用的 struct 很漂亮,也是我要改善再我實作裏面的。

typedef struct { void (*function)(void *); void *argument; } threadpool_task_t;

目前想要嘗試將其改寫成 ring buffer

他已經是Yen-Kuan Wu

首先,我嘗試跟著 mbrossard的腳步重建一個 threadpool,先整理出我的初步構想。

  • create 一個 threadpool後,裡面養著很多 thread並且使用 cond_wait的方式等待 signal
  • 加入一個 task時就去 cond_signal,而且要 assign 給他目前的 function 和 argument

static function

遇到一個神奇的 bug,invalid storage class for function.
上網查的結果都說我少加 {},我就直接 -E去看發現沒有,再來我看 stackoverflow,他說我把 static function 宣告在 header,所以我就把他宣告再 .c裡,這讓我想到該如何實作一個 private 的 OOP,我們可以用 static 的特性將部份東西鎖在 .c檔裡,讓後 header file 當作 API 開出去。

condition variable 用法及實例

我參考我 IPC的書籍,嘗試選寫一份 condition variable 的用法。

淺談 do{ } while (0)

常會看到這寫法,特別在 Linux 核心原始程式碼,在老師修改我的debug.h也出現,目前我看到的兩個用法是第一個

  1. macro 時會有問題,如果你一次 define 一串複合的指令
  2. 再來是可以使用 break,不之你有沒有什麼時候想要跳過某些 code,這是你就必須多設一個 flag,但如果用這個方式直接 break就可以跳過了

主要參考工程師的好朋友:http://stackoverflow.com/questions/257418/do-while-0-what-is-it-good-for

mbrossard/threadpool:pull request 小插曲

FIX: warning: ISO C90 forbids mixed declarations and code [-Wpedantic]

再剛載下來時,compile時發現一些 warning,很像是 C90有特別規定 declaration有需要放在前面,我就順手改一下

但是 push 上去有個東西很神奇網站叫 travis-ci,他很像是個可以把你的 code 拿去 linux 和 mac 上,而且使用不同的 gcc 版本編譯,而我 pull 上去的版本有一沒過,Mac gcc-5,其實我有去看裡面的編譯過程

https://travis-ci.org/mbrossard/threadpool/jobs/164642802

$ make gcc-5 -D_REENTRANT -Wall -pedantic -Isrc -c -o tests/thrdtest.o tests/thrdtest.c make: gcc-5: No such file or directory make: *** [tests/thrdtest.o] Error 1

看起來不太像我的問題,反而比較像是他們 gcc-5 沒裝成功的感覺。

上游已在 Oct 4 修正了 jserv

看到老師的提點git rebase -i,我還在想說要怎麼樣讓他們重新測試哩。

Git rebase -i

tags: yenWu phonebook-concurrency sysprog21