首先,線程池是什么?顧名思義,就是把一堆開辟好的線程放在一個池子里統一管理,就是一個線程池。
其次,為什么要用線程池,難道來一個請求給它申請一個線程,請求處理完了釋放線程不行么?也行,但是如果創建線程和銷毀線程的時間比線程處理請求的時間長,而且請求很多的情況下,我們的CPU資源都浪費在了創建和銷毀線程上了,所以這種方法的效率比較低,於是,我們可以將若干已經創建完成的線程放在一起統一管理,如果來了一個請求,我們從線程池中取出一個線程來處理,處理完了放回池內等待下一個任務,線程池的好處是避免了繁瑣的創建和結束線程的時間,有效的利用了CPU資源。
按照我的理解,線程池的作用和雙緩沖的作用類似,可以完成任務處理的“魚貫”動作。
最后,如何才能創建一個線程池的模型呢,一般需要以下三個參與者:
1、線程池結構,它負責管理多個線程並提供任務隊列的接口
2、工作線程,它們負責處理任務
3、任務隊列,存放待處理的任務
有了三個參與者,下一個問題就是怎么使線程池安全有序的工作,可以使用POSIX中的信號量、互斥鎖和條件變量等同步手段。有了這些認識,我們就可以創建自己的線程池模型,我在github上找了一個比較經典的線程池的例子,有興趣的可以學習一下。
原作者github地址:https://github.com/Pithikos/C-Thread-Pool
線程池所需要的數據結構:
(1)、0/1信號量,用於當任務隊列非空時通知線程,這里是用互斥鎖和條件變量來實現的信號量,其實POSIX信號量的一種實現就是用的互斥鎖和條件變量
/* Binary semaphore */ typedef struct bsem { pthread_mutex_t mutex; pthread_cond_t cond; int v; //v的值非0即1 } bsem;
(2)、標識任務的結構體,prev指向的對象是當前任務的前一個任務,這里用pnext來標識更貼切
/* Job */ typedef struct job{ struct job* prev; /* pointer to previous job */ void* (*function)(void* arg); /* function pointer */ void* arg; /* function's argument */ } job;
(3)、工作隊列
/* Job queue */ typedef struct jobqueue{ pthread_mutex_t rwmutex; /* used for queue r/w access */ job *front; /* pointer to front of queue */ job *rear; /* pointer to rear of queue */ bsem *has_jobs; /* flag as binary semaphore */ int len; /* number of jobs in queue */ } jobqueue;
互斥鎖rwmutex用來同步對工作隊列的讀寫操作,front用來標識工作隊列中的第一個任務,rear用來標識工作隊列中的最后一個任務,has_jobs用來提供對二值信號量的訪問接口,len代表當前工作隊列中的任務數量。
(4)、工作線程
/* Thread */ typedef struct thread{ int id; /* friendly id */ pthread_t pthread; /* pointer to actual thread */ struct thpool_* thpool_p; /* access to thpool */ } thread;
id標識第幾個線程,pthread代表的是創建的真正的線程id,對於每個線程來說,都提供對所在線程池的訪問
(5)、線程池結構
/* Threadpool */ typedef struct thpool_{ thread** threads; /* pointer to threads */ volatile int num_threads_alive; /* threads currently alive */ volatile int num_threads_working; /* threads currently working */ pthread_mutex_t thcount_lock; /* used for thread count etc */ jobqueue* jobqueue_p; /* pointer to the job queue */ } thpool_;
threads可以看做是一個指針數組,數組中的每個指針都指向一個線程結構,num_threads_alive標識的是線程池中有多少個可工作線程,num_threads_working代表的是當前線程池中正在工作的線程數目,互斥鎖thcount_lock提供對線程池數據的互斥訪問,同時,線程池需要和任務隊列協作,所以還要提供對任務隊列的訪問。
線程池的工作流程:
初始化線程池、任務隊列和工作線程->向任務隊列中添加任務->將等候在條件變量(任務隊列上有任務)上的一個線程喚醒並從該任務隊列中取出第一個任務給該線程執行->等待任務隊列中所有任務執行完畢->關閉線程池。
