一個(gè)邏輯完備的線程池
掃描二維碼
隨時(shí)隨地手機(jī)看文章
開(kāi)源項(xiàng)目Workflow中有個(gè)重要的基礎(chǔ)模塊:
代碼僅300行的C語(yǔ)言線程池。
本文會(huì)伴隨源碼分析,而邏輯完備、對(duì)稱(chēng)無(wú)差別的特點(diǎn)于第3部分開(kāi)始
歡迎跳閱, 或直接到Github主頁(yè)上圍觀代碼?
https://github.com/sogou/workflow/blob/master/src/kernel/thrdpool.c
? 0 - Workflow的thrdpool
作為目前Github上炙手可熱的異步調(diào)度引擎
Workflow有一個(gè)大招是:
計(jì)算通信融為一體。
而計(jì)算的核心:Executor調(diào)度器,
就是基于這個(gè)線程池實(shí)現(xiàn)的。
可以說(shuō),一個(gè)通用而高效的線程池,
是我們寫(xiě)C/C++代碼時(shí)離不開(kāi)的基礎(chǔ)模塊。
thrdpool代碼位置在src/kernel/,
不僅可以直接拿來(lái)使用,
同時(shí)也適合閱讀學(xué)習(xí)。
而更重要的,秉承Workflow項(xiàng)目本身
一貫的嚴(yán)謹(jǐn)極簡(jiǎn)的作風(fēng),
這個(gè)thrdpool代碼極致簡(jiǎn)潔,
實(shí)現(xiàn)邏輯上亦非常完備,
結(jié)構(gòu)精巧,處處嚴(yán)謹(jǐn),
復(fù)雜的并發(fā)處理依然可以對(duì)稱(chēng)無(wú)差別,
不得不讓我驚嘆:妙?。。?!?
你可能會(huì)很好奇,
線程池還能寫(xiě)出什么別致的新思路嗎?
先列出一些,你們細(xì)品:
-
特點(diǎn)1:創(chuàng)建完線程池后,無(wú)需記錄任何線程id或?qū)ο?,線程池可以通過(guò)一個(gè)等一個(gè)的方式優(yōu)雅地去結(jié)束所有線程;? 也就是說(shuō),每一個(gè)線程都是對(duì)等的
-
特點(diǎn)2:線程任務(wù)可以由另一個(gè)線程任務(wù)調(diào)起;甚至線程池正在被銷(xiāo)毀時(shí)也可以提交下一個(gè)任務(wù);(這很重要,因?yàn)榫€程本身很可能是不知道線程池的狀態(tài)的;? 即,每一個(gè)任務(wù)也是對(duì)等的
-
特點(diǎn)3:同理,線程任務(wù)也可以銷(xiāo)毀這個(gè)線程池;(非常完整~? 每一種行為也是對(duì)等的,包括destroy
我真的迫不及待為大家深層解讀一下,
這個(gè)我愿稱(chēng)之為“邏輯完備”的線程池。
? 1 - 前置知識(shí)
第一部分我先梳理一些基本內(nèi)容梳理,
有基礎(chǔ)的小伙伴可以直接跳過(guò)。
如果有不準(zhǔn)確的地方,歡迎大家指正交流~
Question: 為什么需要線程池?
其實(shí)思路不僅對(duì)線程池,
對(duì)任何有限資源的調(diào)度管理都是類(lèi)似的。
我們知道,
通過(guò)pthread或者std::thread創(chuàng)建線程,
就可以實(shí)現(xiàn)多線程并發(fā)執(zhí)行我們的代碼。
但是CPU的核數(shù)是固定的,
所以真正并行執(zhí)行的最大值也是固定的,
過(guò)多的線程除了頻繁創(chuàng)建產(chǎn)生overhead以外,
還會(huì)導(dǎo)致對(duì)系統(tǒng)資源進(jìn)行爭(zhēng)搶,
這些都是不必要的浪費(fèi)。
因此我們可以管理有限個(gè)線程,
循環(huán)且合理地利用它們。??
那么線程池一般包含哪些內(nèi)容呢?
- 首先是管理若干個(gè)工具人線程;
- 其次是管理交給線程去執(zhí)行的任務(wù),這個(gè)一般會(huì)有一個(gè)隊(duì)列;
- 再然后線程之間需要一些同步機(jī)制,比如mutex、condition等;
- 最后就是各線程池實(shí)現(xiàn)上自身需要的其他內(nèi)容了;
接下來(lái)我們看看Workflow的thrdpool是怎么做的。
? 2 - 代碼概覽
以下共7步常用思路,
足以讓我們把代碼飛快過(guò)一遍。
第1步:先看頭文件,有什么接口。
我們打開(kāi)thrdpool.h,只需關(guān)注這三個(gè):
// 創(chuàng)建線程池 thrdpool_t *thrdpool_create(size_t nthreads, size_t stacksize); // 把任務(wù)交給線程池的入口 int thrdpool_schedule(const struct thrdpool_task *task, thrdpool_t *pool); // 銷(xiāo)毀線程池 void thrdpool_destroy(void (*pending)(const struct thrdpool_task *), thrdpool_t *pool);
第2步:接口上有什么數(shù)據(jù)結(jié)構(gòu)。
即,我們?nèi)绾蚊枋鲆粋€(gè)交給線程池的任務(wù)。
struct thrdpool_task { void (*routine)(void *); // 函數(shù)指針 void *context; // 上下文 };
第3步:再看實(shí)現(xiàn).c,內(nèi)部數(shù)據(jù)結(jié)構(gòu)。
struct __thrdpool { struct list_head task_queue;// 任務(wù)隊(duì)列 size_t nthreads; // 線程個(gè)數(shù) size_t stacksize; // 構(gòu)造線程時(shí)的參數(shù) pthread_t tid; // 運(yùn)行期間記錄的是個(gè)zero值 pthread_mutex_t mutex; pthread_cond_t cond; pthread_key_t key; pthread_cond_t *terminate; };
沒(méi)有一個(gè)多余,每一個(gè)成員都很到位:
- tid:線程id,整個(gè)線程池只有一個(gè),它不會(huì)奇怪地去記錄任何一個(gè)線程的id,這樣就不完美了?,它平時(shí)運(yùn)行的時(shí)候是空值,退出的時(shí)候,它是用來(lái)實(shí)現(xiàn)鏈?zhǔn)降却年P(guān)鍵。
- mutex 和 cond是常見(jiàn)的線程間同步的工具,其中這個(gè)cond是用來(lái)給生產(chǎn)者和消費(fèi)者去操作任務(wù)隊(duì)列用的。
- key:是線程池的key,然后會(huì)賦予給每個(gè)由線程池創(chuàng)建的線程作為他們的thread local,用于區(qū)分這個(gè)線程是否是線程池創(chuàng)建的。
- 一個(gè)pthread_cond_t *terminate,這有兩個(gè)用途:不僅是退出時(shí)的標(biāo)記位 ,而且還是調(diào)用退出的那個(gè)人要等待的condition。
以上各個(gè)成員的用途,
好像說(shuō)了,又好像沒(méi)說(shuō),?
是因?yàn)?strong>幾乎每一個(gè)成員都值得深挖一下,
所以我們記住它們,
后面看代碼的時(shí)候就會(huì)豁然開(kāi)朗!?
第4步:接口都調(diào)用了什么核心函數(shù)。
thrdpool_t *thrdpool_create(size_t nthreads, size_t stacksize) { thrdpool_t *pool; ret = pthread_key_create(&pool->key, NULL); if (ret == 0) { // 去掉了其他代碼,但是注意到剛才的tid和terminate的賦值 memset(&pool->tid, 0, sizeof (pthread_t)); pool->terminate = NULL; if (__thrdpool_create_threads(nthreads, pool) >= 0) return pool; ...
這里可以看到
__thrdpool_create_threads()里邊
最關(guān)鍵的,就是循環(huán)創(chuàng)建nthreads個(gè)線程。
while (pool->nthreads < nthreads) { ret = pthread_create(&tid, &attr, __thrdpool_routine, pool); ...
第5步:略讀核心函數(shù)的功能。
所以我們?cè)谏弦徊街懒耍?br />
每個(gè)線程執(zhí)行的是__thrdpool_routine()
不難想象,它會(huì)不停從隊(duì)列拿任務(wù)出來(lái)執(zhí)行:
static void *__thrdpool_routine(void *arg) { ... while (1) { // 1. 從隊(duì)列里拿一個(gè)任務(wù)出來(lái),沒(méi)有就等待 pthread_mutex_lock(&pool->mutex); while (!pool->terminate && list_empty(&pool->task_queue)) pthread_cond_wait(&pool->cond, &pool->mutex); // 2. 線程池結(jié)束的標(biāo)志位,記住它,先跳過(guò) if (pool->terminate) break; // 3. 如果能走到這里,恭喜你,拿到了任務(wù)~ entry = list_entry(*pos, struct __thrdpool_task_entry, list); list_del(*pos); // 4. 先解鎖 pthread_mutex_unlock(&pool->mutex); task_routine = entry->task.routine; task_context = entry->task.context; free(entry); // 5. 再執(zhí)行 task_routine(task_context); // 6. 這里也先記住它,意思是線程池里的線程可以銷(xiāo)毀線程池 if (pool->nthreads == 0) { /* Thread pool was destroyed by the task. */ free(pool); return NULL; } } ... // 后面還有魔法,留下一章解讀~~~
第6步:把函數(shù)之間的關(guān)系聯(lián)系起來(lái)。
剛才看到的__thrdpool_routine()
就是線程的核心函數(shù)了,
它可以和誰(shuí)關(guān)聯(lián)起來(lái)呢?
可以和接口thrdpool_schedule()關(guān)聯(lián)上
我們說(shuō)過(guò),線程池上有個(gè)隊(duì)列管理任務(wù):
- 每個(gè)執(zhí)行routine的線程,都是消費(fèi)者
- 每個(gè)發(fā)起schedule的線程,都是生產(chǎn)者
我們已經(jīng)看過(guò)消費(fèi)者了,來(lái)看看生產(chǎn)者的代碼:
inline void __thrdpool_schedule(const struct thrdpool_task *task, void *buf, thrdpool_t *pool) { struct __thrdpool_task_entry *entry = (struct __thrdpool_task_entry *)buf; entry->task = *task; pthread_mutex_lock(&pool->mutex); // 添加到隊(duì)列里 list_add_tail(&entry->list, &pool->task_queue); // 叫醒在等待的線程 pthread_cond_signal(&pool->cond); pthread_mutex_unlock(&pool->mutex); }
說(shuō)到這里,特點(diǎn)2就非常清晰了:
開(kāi)篇說(shuō)的特點(diǎn)2是說(shuō),
”線程任務(wù)可以由另一個(gè)線程任務(wù)調(diào)起”。
只要對(duì)隊(duì)列的管理做得好,
顯然消費(fèi)者所執(zhí)行的函數(shù)里也可以生產(chǎn)
第7步:看其他情況的處理
對(duì)于線程池來(lái)說(shuō)就是比如銷(xiāo)毀的情況。
接口thrdpool_destroy()實(shí)現(xiàn)非常簡(jiǎn)單:
void thrdpool_destroy(void (*pending)(const struct thrdpool_task *), thrdpool_t *pool) { ... // 1. 內(nèi)部會(huì)設(shè)置pool->terminate,并叫醒所有等在隊(duì)列拿任務(wù)的線程 __thrdpool_terminate(in_pool, pool); // 2. 把隊(duì)列里還沒(méi)有執(zhí)行的任務(wù)都拿出來(lái),通過(guò)pending返回給用戶(hù) list_for_each_safe(pos, tmp, &pool->task_queue) { entry = list_entry(pos, struct __thrdpool_task_entry, list); list_del(pos); if (pending) pending(&entry->task); ... // 后面就是銷(xiāo)毀各種內(nèi)存,同樣有魔法~
在退出的時(shí)候,
我們那些已經(jīng)提交但是還沒(méi)有被執(zhí)行的任務(wù)
是絕對(duì)不能就這么扔掉了的,
于是我們可以傳入一個(gè)pending()函數(shù),
上層可以做自己的回收、回調(diào)、
或任何保證上層邏輯完備的事情。
設(shè)計(jì)的完整性,無(wú)處不在。
接下來(lái)我們就可以跟著我們的核心問(wèn)題,
針對(duì)性地看看每個(gè)特點(diǎn)都是怎么實(shí)現(xiàn)的。
? 3 - 特點(diǎn)1: 一個(gè)等待一個(gè)的優(yōu)雅退出
這里提出一個(gè)問(wèn)題:
線程池要退出,如何結(jié)束所有線程?
一般線程池的實(shí)現(xiàn)都是
需要記錄下所有的線程id,
或者thread對(duì)象,
以便于我們?nèi)ビ?strong>join方法等待它們結(jié)束。
不嚴(yán)格地用join收拾干凈會(huì)有什么問(wèn)題?
最直觀的,模塊退出時(shí)很可能會(huì)報(bào)內(nèi)存泄漏
但是我們剛才看,
pool里并沒(méi)有記錄所有的tid呀?
正如開(kāi)篇說(shuō)的,
pool上只有一個(gè)tid,而且還是個(gè)空的值。
而特點(diǎn)1正是Workflow thrdpool的答案:
無(wú)需記錄所有線程,我可以讓線程挨個(gè)自動(dòng)退出、且一個(gè)等待下一個(gè),最終達(dá)到調(diào)用完thrdpool_destroy()后內(nèi)存回收干凈的目的。
這里先給一個(gè)簡(jiǎn)單的圖,
假設(shè)發(fā)起destroy的人是main線程,
我們?nèi)绾巫龅揭粋€(gè)等一個(gè)退出:
步驟如下:
- 線程的退出,由thrdpool_destroy()設(shè)置pool->terminate開(kāi)始。
- 我們每個(gè)線程,在while(1) 里會(huì)第一時(shí)間發(fā)現(xiàn)terminate,線程池要退出了,然后會(huì)break出這個(gè)while循環(huán)。
- 注意這個(gè)時(shí)候,還持有著mutex鎖,我們拿出pool上唯一的那個(gè)tid,放到我的臨時(shí)變量,我會(huì)根據(jù)拿出來(lái)的值做不同的處理。且我會(huì)把我自己的tid放上去,然后再解mutex鎖。
- 那么很顯然,第一個(gè)從pool上拿tid的人,會(huì)發(fā)現(xiàn)這是個(gè)0值,就可以直接結(jié)束了,不用負(fù)責(zé)等待任何其他人,但我在完全結(jié)束之前需要有人負(fù)責(zé)等待我的結(jié)束,所以我會(huì)把我的id放上去。
- 而如果發(fā)現(xiàn)自己從pool里拿到的tid不是0值,說(shuō)明我要負(fù)責(zé)join上一個(gè)人,并且把我的tid放上去,讓下一個(gè)人負(fù)責(zé)我。
- 最后的那個(gè)人,是那個(gè)發(fā)現(xiàn)pool->nthreads為0的人,那么我就可以通過(guò)這個(gè)terminate(它本身是個(gè)condition)去通知發(fā)起destroy的人。
- 最后發(fā)起者就可以退了。?
是不是非常有意思?。?!
非常優(yōu)雅的做法?。?!?
所以我們會(huì)發(fā)現(xiàn),
其實(shí)大家不太需要知道太多信息,
只需要知道我要負(fù)責(zé)的上一個(gè)人。
當(dāng)然每一步都是非常嚴(yán)謹(jǐn)?shù)模?br /> 結(jié)合剛才跳過(guò)的第一段魔法?感受一下:
static void *__thrdpool_routine(void *arg) { while (1) { // 1.注意這里還持有鎖 pthread_mutex_lock(&pool->mutex); ... // 等著隊(duì)列拿任務(wù)出來(lái) // 2. 這既是標(biāo)識(shí)位,也是發(fā)起銷(xiāo)毀的那個(gè)人所等待的condition if (pool->terminate) break; ... // 執(zhí)行拿到的任務(wù) } /* One thread joins another. Don't need to keep all thread IDs. */ // 3. 把線程池上記錄的那個(gè)tid拿下來(lái),我來(lái)負(fù)責(zé)上一人 tid = pool->tid; // 4. 把我自己記錄到線程池上,下一個(gè)人來(lái)負(fù)責(zé)我 pool->tid = pthread_self(); // 5. 每個(gè)人都減1,最后一個(gè)人負(fù)責(zé)叫醒發(fā)起detroy的人 if (--pool->nthreads == 0) pthread_cond_signal(pool->terminate); // 6. 這里可以解鎖進(jìn)行等待了 pthread_mutex_unlock(&pool->mutex); // 7. 只有第一個(gè)人拿到0值 if (memcmp(&tid, &__zero_tid, sizeof (pthread_t)) != 0) // 8. 只要不0值,我就要負(fù)責(zé)等上一個(gè)結(jié)束才能退 pthread_join(tid, NULL); return NULL; // 9. 退出,干干凈凈~ }
? 4 - 特點(diǎn)2:線程任務(wù)可以由另一個(gè)線程任務(wù)調(diào)起
在第二部分我們看過(guò)源碼,
只要隊(duì)列管理得好,
線程任務(wù)里提交下一個(gè)任務(wù)是完全OK的。
這很合理。?
那么問(wèn)題來(lái)了,
特點(diǎn)1又說(shuō),我們每個(gè)線程,
是不需要知道太多線程池的狀態(tài)和信息的。
而線程池的銷(xiāo)毀是個(gè)過(guò)程,
如果在這個(gè)過(guò)程間提交任務(wù)會(huì)怎么樣呢?
因此特點(diǎn)2的一個(gè)重要解讀是:
線程池被銷(xiāo)毀時(shí)也可以提交下一個(gè)任務(wù)。
而且剛才提過(guò),
還沒(méi)有被執(zhí)行的任務(wù),
可以通過(guò)我們傳入的pending()函數(shù)拿回來(lái)。
簡(jiǎn)單看看銷(xiāo)毀時(shí)的嚴(yán)謹(jǐn)做法:
static void __thrdpool_terminate(int in_pool, thrdpool_t *pool) { pthread_cond_t term = PTHREAD_COND_INITIALIZER; pthread_mutex_lock(&pool->mutex); // 1. 加鎖設(shè)置標(biāo)志位,之后的添加任務(wù)不會(huì)被執(zhí)行,但可以pending拿到 pool->terminate = &term; // 2. 廣播所有等待的消費(fèi)者 pthread_cond_broadcast(&pool->cond); if (in_pool) // 3. 這里的魔法等下講>_<~ { /* Thread pool destroyed in a pool thread is legal. */ pthread_detach(pthread_self()); pool->nthreads--; } // 4. 如果還有線程沒(méi)有退完,我會(huì)等,注意這里是while while (pool->nthreads > 0) pthread_cond_wait(&term, &pool->mutex); pthread_mutex_unlock(&pool->mutex); // 5.同樣地等待打算退出的上一個(gè)人 if (memcmp(&pool->tid, &__zero_tid, sizeof (pthread_t)) != 0) pthread_join(pool->tid, NULL); }
? 5 - 特點(diǎn)3:同樣可以在線程任務(wù)里銷(xiāo)毀這個(gè)線程池
既然線程任務(wù)可以做任何事情,
理論上,線程任務(wù)也可以銷(xiāo)毀線程池?
作為一個(gè)邏輯完備的線程池,
大膽一點(diǎn),
我們把問(wèn)號(hào)去掉。
而且,銷(xiāo)毀并不會(huì)結(jié)束當(dāng)前任務(wù),
它會(huì)等這個(gè)任務(wù)執(zhí)行完。
想象一下,剛才的__thrdpool_routine(),
while(1)里拿出來(lái)的那個(gè)任務(wù),
做的事情竟然是發(fā)起thrdpool_destroy()...
把上面的圖大膽改一下:?
如果發(fā)起銷(xiāo)毀的人,
是我們自己內(nèi)部的線程,
那么我們就不是等n個(gè),而是等n-1,
少了一個(gè)外部線程等待我們。
如何實(shí)現(xiàn)才能讓這些邏輯都完美融合呢?
我們把剛才跳過(guò)的三段魔法串起來(lái)看看。
? 第一段魔法,銷(xiāo)毀的發(fā)起者。
如果發(fā)起銷(xiāo)毀的人是線程池內(nèi)部的線程,
那么它具有較強(qiáng)的自我管理意識(shí)
(因?yàn)榍懊嬲f(shuō)了,會(huì)等它這個(gè)任務(wù)執(zhí)行完)
而我們可以放心大膽地pthread_detach,
無(wú)需任何人join它等待它結(jié)束。
static void __thrdpool_terminate(int in_pool, thrdpool_t *pool) { ... // 每個(gè)由線程池創(chuàng)建的線程都設(shè)置了一個(gè)key,由此判斷是否是in_pool if (in_pool) { /* Thread pool destroyed in a pool thread is legal. */ pthread_detach(pthread_self()); pool->nthreads--; }
? 第二段魔法:線程池誰(shuí)來(lái)free?
一定是發(fā)起銷(xiāo)毀的那個(gè)人。
所以這里用in_pool來(lái)判斷是否是外部的人:
void thrdpool_destroy(void (*pending)(const struct thrdpool_task *), thrdpool_t *pool) { // 已經(jīng)調(diào)用完第一段,且挨個(gè)pending(未執(zhí)行的task)了 // 銷(xiāo)毀其他內(nèi)部分配的內(nèi)存 ... // 如果不是內(nèi)部線程發(fā)起的銷(xiāo)毀,要負(fù)責(zé)回收線程池內(nèi)存 if (!in_pool) free(pool); }
那現(xiàn)在不是main線程發(fā)起的銷(xiāo)毀呢?
發(fā)起的銷(xiāo)毀的那個(gè)內(nèi)部線程,
怎么能保證我可以在最后關(guān)頭
把所有資源回收干凈、調(diào)free(pool)、
最后功成身退呢?
在前面閱讀源碼第5步,其實(shí)我們看過(guò),
__thrdpool_routine()里有free的地方。
于是現(xiàn)在三段魔法終于串起來(lái)了。
? 第三段魔法:嚴(yán)謹(jǐn)?shù)牟l(fā)。
static void *__thrdpool_routine(void *arg) { while (1) { // ... task_routine(task_context); // 如果routine里做的事情,是銷(xiāo)毀線程池... // 注意這個(gè)時(shí)候,其他內(nèi)存都已經(jīng)被destroy里清掉了,萬(wàn)萬(wàn)不可以再用什么mutex、cond if (pool->nthreads == 0) { /* Thread pool was destroyed by the task. */ free(pool); return NULL; } ...
非常重要的一點(diǎn),
由于并發(fā),我們是不知道誰(shuí)先操作的。
假設(shè)我們稍微改一改這個(gè)順序,
就又是另一番邏輯。
比如我作為一個(gè)內(nèi)部線程,
在routine()里調(diào)用destroy()期間,
發(fā)現(xiàn)還有線程沒(méi)有執(zhí)行完,
我就要等在我的terminate上,
待最后看到nthreads==0的那個(gè)人叫醒我。
然后,我的代碼繼續(xù)執(zhí)行,
函數(shù)棧就會(huì)從destroy()回到routine(),
也就是上面那幾行,
再然后就可以free(pool);
由于這時(shí)候我已經(jīng)放飛自我detach了,
于是一切順利結(jié)束。
你看,無(wú)論如何都可以完美地銷(xiāo)毀線程池:
并發(fā)是復(fù)雜多變的,代碼是簡(jiǎn)潔統(tǒng)一的
是不是太妙了!
我寫(xiě)到這里已經(jīng)要感動(dòng)哭了!?
? 6 - 簡(jiǎn)單的用法
這個(gè)線程池只有兩個(gè)文件:
thrdpool.h和thrdpool.c,
而且只依賴(lài)內(nèi)核的數(shù)據(jù)結(jié)構(gòu)list.h。
我們把它拿出來(lái)玩,自己寫(xiě)一段代碼:
void my_routine(void *context) { // 我們要執(zhí)行的函數(shù) printf("task-%llu start.\n", reinterpret_cast<unsigned long long>(context); ); } void my_pending(const struct thrdpool_task *task) { // 線程池銷(xiāo)毀后,沒(méi)執(zhí)行的任務(wù)會(huì)到這里 printf("pending task-%llu.\n", reinterpret_cast<unsigned long long>(task->context);); } int main() { thrdpool_t *thrd_pool = thrdpool_create(3, 1024); // 創(chuàng)建 struct thrdpool_task task; unsigned long long i; for (i = 0; i < 5; i++) { task.routine = &my_routine; task.context = reinterpret_cast<void *>(i); thrdpool_schedule(&task, thrd_pool); // 調(diào)用 } getchar(); // 卡住主線程,按回車(chē)?yán)^續(xù) thrdpool_destroy(&my_pending, thrd_pool); // 結(jié)束 return 0; }
再打印幾行l(wèi)og,直接編譯就可以跑起來(lái):
簡(jiǎn)單程度堪比大一上學(xué)期C語(yǔ)言作業(yè)。?
? 7 - 并發(fā)與結(jié)構(gòu)之美
最后談?wù)劯惺堋?
看完之后我很后悔為什么沒(méi)有早點(diǎn)看
為什么不早點(diǎn)就可以獲得知識(shí)的感覺(jué),
并且在淺層看懂之際,
我知道自己肯定沒(méi)有完全理解到里邊的精髓,
畢竟我不能深刻地理解到
設(shè)計(jì)者當(dāng)時(shí)對(duì)并發(fā)的構(gòu)思和模型上的選擇。
我只能說(shuō),
沒(méi)有十多年頂級(jí)的系統(tǒng)調(diào)用和并發(fā)編程的功底
寫(xiě)不出這樣的代碼,
沒(méi)有極致的審美與對(duì)品控的偏執(zhí)
也寫(xiě)不出這樣的代碼。
并發(fā)編程有很多說(shuō)道,
就正如退出這個(gè)這么簡(jiǎn)單的事情,
想要做到退出時(shí)回收干凈卻很難。
如果說(shuō)你寫(xiě)業(yè)務(wù)邏輯自己管線程,
退出什么的sleep(1)都無(wú)所謂,
但如果說(shuō)做框架的人
不能把自己的框架做得完美無(wú)暇邏輯自洽
就難免讓人感覺(jué)差點(diǎn)意思。
而這個(gè)thrdpool,它作為一個(gè)線程池,
是如此的邏輯完備,
用最對(duì)稱(chēng)簡(jiǎn)潔的方式去面對(duì)復(fù)雜的并發(fā)。
再次讓我深深地感到震撼:
我們身邊那些原始的、底層的、基礎(chǔ)的代碼,
還有很多新思路,
還可以寫(xiě)得如此美。
Workflow項(xiàng)目源碼地址:
https://github.com/sogou/workflow





