帶你深入理解一個Linux下C線程池的實現

關於線程和線程池的學習,我們可以從以下幾個方面入手

第一,什麼是線程,線程和進程的區別是什麼

第二,線程中的基本概念,線程的生命週期

第三,單線程和多線程

第四,線程池的原理解析

第五,常見的幾種線程池的特點以及各自的應用場景

一、什麼是線程,線程和進程的區別是什麼

線程,程序從行流的最小執行單位,是行程中的實際運作單位,經常容易和進程這個概念混淆。那麼,線程和進程究竟有什麼區別呢?首先,進程是一個動態的過程,是一個活動的實體。簡單來説,一個應用程序的運行就可以被看做是一個進程,而線程,是運行中的實際的任務執行者。可以説,進程中包含了多個可以同時運行的線程。

二、線程中的基本概念,線程的生命週期

線程的生命週期,線程的生命週期可以利用以下的圖解來更好的理解:

帶你深入理解一個Linux下C線程池的實現
三、什麼是單線程和多線程?

一般來説實現一個線程池主要包括以下幾個組成部分:

1)線程管理器:用於創建並管理線程池。

2)工作線程:線程池中實際執行任務的線程。在初始化線程時會預先創建好固定數目的線程在池中,這些初始化的線程一般處於空閒狀態,一般不佔用CPU,佔用較小的內存空間。

3)任務接口:每個任務必須實現的接口,當線程池的任務隊列中有可執行任務時,被空閒的工作線程調去執行,把任務抽象出來形成接口,可以做到線程池與具體的任務無關。

4)任務隊列:用來存放沒有處理的任務,提供一種緩衝機制,實現這種結構有好幾種方法,常用的是隊列,主要運用先進先出原理,另外一種是鏈表之類的數據結構,

什麼時候需要創建線程池呢?簡單的説,如果一個應用需要頻繁的創建和銷燬線程,而任務執行的時間又非常短,這樣線程創建和銷燬的帶來的開銷就不容忽視,這時也是線程池該出場的機會了。如果線程創建和銷燬時間相比任務執行時間可以忽略不計,則沒有必要使用線程池了。 下面是Linux系統下用C語言創建的一個線程池。線程池會維護一個任務鏈表(每個CThread_worker結構就是一個任務)。 pool_init()函數預先創建好max_thread_num個線程,每個線程執thread_routine ()函數。該函數中

while (pool->cur_queue_size == 0){      pthread_cond_wait (&(pool->queue_ready),&(pool->queue_lock));}

示如果任務鏈表中沒有任務,則該線程處於阻塞等待狀態。否則從隊列中取出任務並執行。 pool_add_worker()函數向線程池的任務鏈表中加入一個任務,加入後通過調用pthread_cond_signal (&(pool->queue_ready))喚醒一個處於阻塞狀態的線程(如果有的話)。 pool_destroy ()函數用於銷燬線程池,線程池任務鏈表中的任務不會再被執行,但是正在運行的線程會一直把任務運行完後再退出。

#include #include #include #include #include #include /**線程池裏所有運行和等待的任務都是一個CThread_worker*由於所有任務都在鏈表裏,所以是一個鏈表結構*/typedef struct worker{/*回調函數,任務運行時會調用此函數,注意也可聲明成其它形式*/void *(*process) (void *arg);void *arg;/*回調函數的參數*/struct worker *next;} CThread_worker;/*線程池結構*/typedef struct{pthread_mutex_t queue_lock;pthread_cond_t queue_ready;/*鏈表結構,線程池中所有等待任務*/CThread_worker *queue_head;/*是否銷燬線程池*/int shutdown;pthread_t *threadid;/*線程池中允許的活動線程數目*/int max_thread_num;/*當前等待隊列的任務數目*/int cur_queue_size;} CThread_pool;int pool_add_worker (void *(*process) (void *arg), void *arg);void *thread_routine (void *arg);//share resourcestatic CThread_pool *pool = NULL;voidpool_init (int max_thread_num){pool = (CThread_pool *) malloc (sizeof (CThread_pool));pthread_mutex_init (&(pool->queue_lock), NULL);pthread_cond_init (&(pool->queue_ready), NULL);pool->queue_head = NULL;pool->max_thread_num = max_thread_num;pool->cur_queue_size = 0;pool->shutdown = 0;pool->threadid = (pthread_t *) malloc (max_thread_num * sizeof (pthread_t));int i = 0;for (i = 0; i < max_thread_num; i++){pthread_create (&(pool->threadid[i]), NULL, thread_routine,NULL);}}/*向線程池中加入任務*/intpool_add_worker (void *(*process) (void *arg),void *arg){/*構造一個新任務*/CThread_worker *newworker = (CThread_worker *) malloc (sizeof (CThread_worker));newworker->process = process;newworker->arg = arg;newworker->next = NULL;/*別忘置空*/pthread_mutex_lock (&(pool->queue_lock));/*將任務加入到等待隊列中*/CThread_worker *member = pool->queue_head;if (member != NULL){while (member->next != NULL)member = member->next;member->next = newworker;}else{pool->queue_head = newworker;}assert (pool->queue_head != NULL);pool->cur_queue_size++;pthread_mutex_unlock (&(pool->queue_lock));/*好了,等待隊列中有任務了,喚醒一個等待線程;注意如果所有線程都在忙碌,這句沒有任何作用*/pthread_cond_signal (&(pool->queue_ready));return 0;}/*銷燬線程池,等待隊列中的任務不會再被執行,但是正在運行的線程會一直把任務運行完後再退出*/intpool_destroy (){if (pool->shutdown)return -1;/*防止兩次調用*/pool->shutdown = 1;/*喚醒所有等待線程,線程池要銷燬了*/pthread_cond_broadcast (&(pool->queue_ready));/*阻塞等待線程退出,否則就成殭屍了*/int i;for (i = 0; i < pool->max_thread_num; i++)pthread_join (pool->threadid[i], NULL);free (pool->threadid);/*銷燬等待隊列*/CThread_worker *head = NULL;while (pool->queue_head != NULL){head = pool->queue_head;pool->queue_head = pool->queue_head->next;free (head);}/*條件變量和互斥量也別忘了銷燬*/pthread_mutex_destroy(&(pool->queue_lock));pthread_cond_destroy(&(pool->queue_ready));free (pool);/*銷燬後指針置空是個好習慣*/pool=NULL;return 0;}void *thread_routine (void *arg){printf ("starting thread 0x%x\n",pthread_self ());while (1){pthread_mutex_lock (&(pool->queue_lock));/*如果等待隊列為0並且不銷燬線程池,則處於阻塞狀態; 注意pthread_cond_wait是一個原子操作,等待前會解鎖,喚醒後會加鎖*/while (pool->cur_queue_size == 0 && !pool->shutdown){printf ("thread 0x%x is waiting\n", pthread_self ());pthread_cond_wait (&(pool->queue_ready), &(pool->queue_lock));}/*線程池要銷燬了*/if (pool->shutdown){/*遇到break,continue,return等跳轉語句,千萬不要忘記先解鎖*/pthread_mutex_unlock (&(pool->queue_lock));printf ("thread 0x%x will exit\n", pthread_self ());pthread_exit (NULL);}printf ("thread 0x%x is starting to work\n", pthread_self ());/*assert是調試的好幫手*/assert (pool->cur_queue_size != 0);assert (pool->queue_head != NULL);/*等待隊列長度減去1,並取出鏈表中的頭元素*/pool->cur_queue_size--;CThread_worker *worker = pool->queue_head;pool->queue_head = worker->next;pthread_mutex_unlock (&(pool->queue_lock));/*調用回調函數,執行任務*/(*(worker->process)) (worker->arg);free (worker);worker = NULL;}/*這一句應該是不可達的*/pthread_exit (NULL);}// 下面是測試代碼void *myprocess (void *arg){printf ("threadid is 0x%x, working on task %d\n", pthread_self (),*(int *) arg);sleep (1);/*休息一秒,延長任務的執行時間*/return NULL;}intmain (int argc, char **argv){pool_init (3);/*線程池中最多三個活動線程*//*連續向池中投入10個任務*/int *workingnum = (int *) malloc (sizeof (int) * 10);int i;for (i = 0; i < 10; i++){workingnum[i] = i;pool_add_worker (myprocess, &workingnum;[i]);}/*等待所有任務完成*/sleep (5);/*銷燬線程池*/pool_destroy ();free (workingnum);return 0;}

將上述所有代碼放入threadpool.c文件中,在Linux輸入編譯命令$ gcc -o threadpool threadpool.c -lpthread以下是運行結果

starting thread 0xb7df6b90thread 0xb7df6b90 is waitingstarting thread 0xb75f5b90thread 0xb75f5b90 is waitingstarting thread 0xb6df4b90thread 0xb6df4b90 is waitingthread 0xb7df6b90 is starting to workthreadid is 0xb7df6b90, working on task 0thread 0xb75f5b90 is starting to workthreadid is 0xb75f5b90, working on task 1thread 0xb6df4b90 is starting to workthreadid is 0xb6df4b90, working on task 2thread 0xb7df6b90 is starting to workthreadid is 0xb7df6b90, working on task 3thread 0xb75f5b90 is starting to workthreadid is 0xb75f5b90, working on task 4thread 0xb6df4b90 is starting to workthreadid is 0xb6df4b90, working on task 5thread 0xb7df6b90 is starting to workthreadid is 0xb7df6b90, working on task 6thread 0xb75f5b90 is starting to workthreadid is 0xb75f5b90, working on task 7thread 0xb6df4b90 is starting to workthreadid is 0xb6df4b90, working on task 8thread 0xb7df6b90 is starting to workthreadid is 0xb7df6b90, working on task 9thread 0xb75f5b90 is waitingthread 0xb6df4b90 is waitingthread 0xb7df6b90 is waitingthread 0xb75f5b90 will exitthread 0xb6df4b90 will exitthread 0xb7df6b90 will exit
總結;為了更好的控制多線程,JDK提供了一套線程框架Executor,幫助開發人員有效的進行線程控制。他們都在java.util.concurrent包中,是JDK併發包的核心。其中一個比較重要的類:Executors,他扮演着線程工廠的角色,我們通過Executors可以創建特定功能的線程池。

不積跬步無以至千里,學習C/C++,Linux,Nginx,golang,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,ffmpeg,流媒體, 音視頻,CDN,P2P,K8S,Docker,Golang,TCP/IP,協程,嵌入式,ARM,DPDK等等。。。

可以後台私信‘資料’即可領取相關學習資料

版權聲明:本文源自 網絡, 於,由 楠木軒 整理發佈,共 6814 字。

轉載請註明: 帶你深入理解一個Linux下C線程池的實現 - 楠木軒