執行緒池的原理與C語言實現

舟清颺發表於2024-06-11

V1.0 2024年6月11日 釋出於部落格園

目錄

目錄
  • 目錄
  • 執行緒池原理
    • 執行緒池是什麼
    • 執行緒池解決的問題
    • 動態建立子執行緒的缺點
  • 執行緒池相關介面
    • 執行緒池相關結構體
      • struct task 任務節點
    • 執行緒池介面
      • init_pool() 執行緒池初始化
        • 執行緒池初始化流程圖
      • add_task() 向執行緒池新增任務
      • add_thread() 增加活躍執行緒
      • remove_thread()刪除活躍執行緒
      • destroy_pool()銷燬執行緒池
  • 執行緒池例項
    • main.c
      • 主函式流程圖
    • thread_pool.h
    • thread_pool.c
      • 執行緒執行的任務函式流程圖
      • 銷燬執行緒池流程圖
  • 參考

執行緒池原理

執行緒池是什麼

執行緒池(Thread Pool)是一種基於池化思想管理執行緒的工具,經常出現在多執行緒伺服器中,如MySQL。

執行緒過多會帶來額外的開銷,其中包括建立銷燬執行緒的開銷、排程執行緒的開銷等等,同時也降低了計算機的整體效能。執行緒池維護多個執行緒,等待監督管理者分配可併發執行的任務。這種做法,一方面避免了處理任務時建立銷燬執行緒開銷的代價,另一方面避免了執行緒數量膨脹導致的過分排程問題,保證了對核心的充分利用。

image

image


執行緒池模型(同程序池):

image

多個子執行緒處理同一個客戶連線上的不同任務

image


使用執行緒池可以帶來一系列好處:

  • 降低資源消耗(系統資源):透過池化技術重複利用已建立的執行緒,降低執行緒建立和銷燬造成的損耗。
  • 提高執行緒的可管理性(系統資源):執行緒是稀缺資源,如果無限制建立,不僅會消耗系統資源,還會因為執行緒的不合理分佈導致資源排程失衡,降低系統的穩定性。使用執行緒池可以進行統一的分配、調優和監控。
  • 提高響應速度(任務響應):任務到達時,無需等待執行緒建立即可立即執行。
  • 提供更多更強大的功能(功能擴充套件):執行緒池具備可擴充性,允許開發人員向其中增加更多的功能。比如延時定時執行緒池ScheduledThreadPoolExecutor,就允許任務延期執行或定期執行。

執行緒池解決的問題

執行緒池解決的核心問題就是資源管理問題。在併發環境下,系統不能夠確定在任意時刻中,有多少任務需要執行,有多少資源需要投入。這種不確定性將帶來以下若干問題:

  • 頻繁申請/銷燬資源和排程資源,將帶來額外的消耗,可能會非常巨大。
  • 對資源無限申請缺少抑制手段,易引發系統資源耗盡的風險。
  • 系統無法合理管理內部的資源分佈,會降低系統的穩定性。

動態建立子執行緒的缺點

透過動態建立子程序(或子執行緒)來實現併發伺服器,這樣做有如下缺點:

  • 動態建立程序(或執行緒)是比較耗費時間的,這將導致較慢的客戶響應。
  • 動態建立的子程序(或子執行緒)通常只用來為一個客戶服務(除非我們做特殊的處理),這將導致系統上產生大量的細微程序(或執行緒)。程序(或執行緒)間的切換將消耗大量CPU時間。
  • 動態建立的子程序是當前程序的完整映像。當前程序必須謹慎地管理其分配的檔案描述符和堆記憶體等系統資源,否則子程序可能複製這些資源,從而使系統的可用資源急劇下降,進而影響伺服器的效能。

執行緒池相關介面

執行緒池相關結構體

struct task 任務節點

image

// 任務結點  單向連結串列的節點,型別
struct task
{
    void *(*do_task)(void *arg); // 任務函式指標  指向執行緒要執行的任務  格式是固定的
    void *arg;					 // 需要傳遞給任務的引數,如果不需要,則NULL

    struct task *next; // 指向下一個任務結點的指標
};

執行緒池介面

init_pool() 執行緒池初始化

image

// 初始化執行緒池 pool執行緒池指標  threads_number 初始化執行緒的個數
bool init_pool(thread_pool *pool, unsigned int threads_number)
{
    // 初始化互斥鎖
    pthread_mutex_init(&pool->lock, NULL);

    // 初始化條件量
    pthread_cond_init(&pool->cond, NULL);

    // 銷燬標誌 設定執行緒池為未關閉狀態
    pool->shutdown = false; // 不銷燬

    // 給任務連結串列的節點申請堆記憶體
    pool->task_list = malloc(sizeof(struct task));

    // 申請堆記憶體,用於儲存建立出來的執行緒的ID
    pool->tids = malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS);

    // 錯誤處理,對malloc進行錯誤處理
    if (pool->task_list == NULL || pool->tids == NULL)
    {
        perror("分配記憶體錯誤");
        return false;
    }

    // 對任務連結串列中的節點的指標域進行初始化
    pool->task_list->next = NULL;

    // 設定執行緒池中處於等待狀態的任務數量最大值
    pool->max_waiting_tasks = MAX_WAITING_TASKS;

    // 設定等待執行緒處理的任務的數量為0,說明現在沒有任務
    pool->waiting_tasks = 0;

    // 設定執行緒池中活躍的執行緒的數量
    pool->active_threads = threads_number;

    int i;

    // 迴圈建立活躍執行緒
    for (i = 0; i < pool->active_threads; i++)
    {
        // 建立執行緒  把執行緒的ID儲存在申請的堆記憶體
        if (pthread_create(&((pool->tids)[i]), NULL,
                           routine, (void *)pool) != 0)
        {
            perror("建立執行緒錯誤");
            return false;
        }
    }

    return true;
}

執行緒池初始化流程圖

mermaid

graph TD A[初始化執行緒池] --> B[初始化互斥鎖] B --> C[初始化條件變數] C --> D[分配任務連結串列記憶體] D --> E[分配執行緒ID陣列記憶體] E --> F{記憶體分配是否成功?} F -- 否 --> G[列印錯誤資訊] F -- 是 --> H[設定初始值] H --> I[建立指定數量執行緒] I --> J[執行緒池初始化完成]

add_task() 向執行緒池新增任務

image

// 向執行緒池的任務連結串列中新增任務
bool add_task(thread_pool *pool,
              void *(*do_task)(void *arg), void *arg)
{
    // 給任務連結串列節點申請記憶體
    struct task *new_task = malloc(sizeof(struct task));
    if (new_task == NULL) // 檢查記憶體分配是否成功
    {
        perror("申請記憶體錯誤");
        return false;
    }

    new_task->do_task = do_task; // 設定任務函式指標
    new_task->arg = arg;		 // 設定任務引數
    new_task->next = NULL;		 // 指標域設定為NULL  初始化任務的下一個指標

    //============ LOCK =============//
    pthread_mutex_lock(&pool->lock); // 加鎖,保護共享資源
    //===============================//

    // 說明要處理的任務的數量大於能處理的任務數量
    if (pool->waiting_tasks >= MAX_WAITING_TASKS) // 檢查等待任務是否超過最大值
    {
        pthread_mutex_unlock(&pool->lock); // 解鎖

        fprintf(stderr, "任務太多.\n"); // 列印錯誤資訊
        free(new_task);					// 釋放新任務記憶體

        return false;
    }

    struct task *tmp = pool->task_list; // 獲取任務連結串列頭

    // 遍歷連結串列,找到單向連結串列的尾節點
    while (tmp->next != NULL)
        tmp = tmp->next;

    // 把新的要處理的任務插入到連結串列的尾部  尾插
    tmp->next = new_task;

    // 要處理的任務的數量+1 (等待任務數量+1)
    pool->waiting_tasks++;

    //=========== UNLOCK ============//
    pthread_mutex_unlock(&pool->lock); // 解鎖
    //===============================//

    // 喚醒第一個處於阻塞佇列中的執行緒
    pthread_cond_signal(&pool->cond);
    return true;
}

add_thread() 增加活躍執行緒

image

// 向執行緒池加入新執行緒
int add_thread(thread_pool *pool, unsigned additional_threads)
{
    // 判斷需要新增的新執行緒的數量是否為0  如果沒有要新增的執行緒,直接返回
    if (additional_threads == 0)
        return 0;

    // 計算執行緒池中匯流排程的數量
    unsigned total_threads =
        pool->active_threads + additional_threads;

    int i, actual_increment = 0; // 初始化計數器

    // 迴圈建立新執行緒
    for (i = pool->active_threads; i < total_threads && i < MAX_ACTIVE_THREADS; i++)
    {
        // 建立新執行緒
        if (pthread_create(&((pool->tids)[i]),
                           NULL, routine, (void *)pool) != 0)
        {
            perror("增加活躍執行緒錯誤"); // 列印錯誤資訊

            // 如果沒有成功建立任何執行緒,返回錯誤
            if (actual_increment == 0)
                return -1;

            break; // 退出迴圈
        }
        actual_increment++; // 增加計數器
    }
    // 記錄此時執行緒池中活躍執行緒的總數
    pool->active_threads += actual_increment; // 更新活躍執行緒數
    return actual_increment;				  // 返回實際增加的執行緒數
}

remove_thread()刪除活躍執行緒

image

// 從執行緒池中刪除執行緒
int remove_thread(thread_pool *pool, unsigned int removing_threads)
{
    if (removing_threads == 0)
        return pool->active_threads; // 如果沒有要刪除的執行緒,直接返回

    int remaining_threads = pool->active_threads - removing_threads;   // 計算剩餘執行緒數
    remaining_threads = remaining_threads > 0 ? remaining_threads : 1; // 確保至少有一個執行緒

    int i;
    for (i = pool->active_threads - 1; i > remaining_threads - 1; i--) // 迴圈取消執行緒
    {
        errno = pthread_cancel(pool->tids[i]); // 取消執行緒

        if (errno != 0) // 檢查取消是否成功
            break;

    }

    if (i == pool->active_threads - 1) // 如果沒有成功取消任何執行緒,返回錯誤
        return -1;
    else
    {
        pool->active_threads = i + 1; // 更新活躍執行緒數
        return i + 1;				  // 返回剩餘執行緒數
    }
}

destroy_pool()銷燬執行緒池

image

// 銷燬執行緒池
bool destroy_pool(thread_pool *pool)
{
    // 1,啟用所有執行緒 設定關閉標誌
    pool->shutdown = true;
    pthread_cond_broadcast(&pool->cond); // 喚醒所有等待中的執行緒

    // 2, 等待執行緒們執行完畢
    int i;
    for (i = 0; i < pool->active_threads; i++) // 迴圈等待所有執行緒退出
    {
        /**
		 * pthread_join(pool->tids[i], NULL) 的作用是等待執行緒池中第 i 個執行緒終止,並清理其相關資源。透過這種方式,可以確保在銷燬執行緒池時,所有執行緒都已經安全地終止。
		 * pthread_join 是 POSIX 執行緒庫中的一個函式,用於等待一個執行緒的終止。它的功能類似於程序中的 wait 系統呼叫。
		 */
        errno = pthread_join(pool->tids[i], NULL); // 等待執行緒退出
        if (errno != 0)							   // 檢查等待是否成功
        {
            printf("join tids[%d] error: %s\n",
                   i, strerror(errno)); // 列印錯誤資訊
        }
        else
            printf("[%u] is joined\n", (unsigned)pool->tids[i]); // 列印執行緒退出資訊
    }

    // 3, 銷燬執行緒池
    free(pool->task_list); // 釋放任務連結串列記憶體
    free(pool->tids);	   // 釋放執行緒ID陣列記憶體
    free(pool);			   // 釋放執行緒池結構體記憶體

    return true;
}

執行緒池例項

main.c

#include "thread_pool.h" // 包含執行緒池標頭檔案

// 任務函式, 列印一次執行緒任務資訊,並等待n秒,模擬真正的執行緒任務
void *mytask(void *arg)
{
    int n = (int)arg; // 要執行的秒數 將引數轉換為整數, 強制轉換才能使用

    /**
	 * %u:無符號整數(unsigned int)
	 * pthread_self():這是一個 POSIX 執行緒庫函式,返回撥用它的執行緒的執行緒 ID。
	 * __FUNCTION__:這是一個預定義的宏,擴充套件為當前函式的名稱。它在除錯和日誌記錄時非常有用,可以顯示當前正在執行的函式名。
	 */
    printf("[%u][%s] ==>工作將會在這裡被執行 %d 秒...\n",
           (unsigned)pthread_self(), __FUNCTION__, n); // 列印任務開始資訊

    sleep(n);

    printf("[%u][%s] ==> 工作完畢!\n",
           (unsigned)pthread_self(), __FUNCTION__); // 列印任務完成資訊

    return NULL;
}
// 計時函式
void *count_time(void *arg)
{
    int i = 0; // 初始化計數器
    while (1)
    {
        sleep(1);
        printf("sec: %d\n", ++i); // 列印經過的秒數
    }
}

int main(void)
{
    pthread_t a;								// 定義一個執行緒ID
    pthread_create(&a, NULL, count_time, NULL); // 建立計時執行緒

    // 1, 初始化執行緒池
    thread_pool *pool = malloc(sizeof(thread_pool)); // 分配記憶體給 執行緒池管理結構體
    init_pool(pool, 2);								 // 初始化執行緒池,建立2個執行緒

    // 2, 新增任務
    printf("向執行緒池中投送3個任務...\n");
    /**
	 * rand() 是 C 標準庫函式,定義在 <stdlib.h> 標頭檔案中。它返回一個偽隨機數,
	 * 		 範圍在 0 到 RAND_MAX 之間,RAND_MAX 是一個宏,通常定義為 32767。
	 *
	 * rand() % 10 的結果是 rand() 產生的隨機數對 10 取模的結果,也就是說,它會返回一個 0 到 9 之間的整數(包括 0 和 9)
	 *
	 * 執行緒函式和任務函式通常需要一個 void * 型別的引數,以便能夠傳遞任意型別的資料。在這種情況下,任務函式 mytask 需要一個 void * 型別的引數。
	 */
    add_task(pool, mytask, (void *)(rand() % 10));
    add_task(pool, mytask, (void *)(rand() % 10));
    add_task(pool, mytask, (void *)(rand() % 10));

    // 3, 檢查活躍執行緒數量
    printf("當前活躍的執行緒數量: %d\n",
           remove_thread(pool, 0)); // 列印當前活躍執行緒數
    sleep(9);						// 等待9秒

    // 4, 新增更多工
    printf("向執行緒池中投送2個任務...\n"); // 列印資訊
    add_task(pool, mytask, (void *)(rand() % 10));
    add_task(pool, mytask, (void *)(rand() % 10));

    // 5, 新增執行緒
    add_thread(pool, 2); // 新增2個執行緒

    sleep(5); // 等待5秒

    // 6, 刪除執行緒
    printf("從執行緒池中刪除3個活躍執行緒, "
           "當前執行緒數量: %d\n",
           remove_thread(pool, 3));

    // 7, 銷燬執行緒池
    destroy_pool(pool); // 銷燬執行緒池
    return 0;			// 程式正常結束
}

實際使用時, 只需要將上述程式碼中的 mytask 函式修改為我們需要實現的功能函式即可

主函式流程圖

graph TD A[主函式開始] --> B[定義執行緒ID] B --> C[建立計時執行緒] C --> D[初始化執行緒池] D --> E[分配記憶體給執行緒池] E --> F[初始化執行緒池,建立2個執行緒] F --> G[新增任務] G --> H[列印資訊: throwing 3 tasks...] H --> I[新增任務1] I --> J[新增任務2] J --> K[新增任務3] K --> L[檢查活躍執行緒數量] L --> M[列印當前活躍執行緒數] M --> N[等待9秒] N --> O[新增更多工] O --> P[列印資訊: throwing another 2 tasks...] P --> Q[新增任務4] Q --> R[新增任務5] R --> S[新增執行緒] S --> T[新增2個執行緒] T --> U[等待5秒] U --> V[刪除執行緒] V --> W[列印資訊: remove 3 threads...] W --> X[刪除3個執行緒] X --> Y[銷燬執行緒池] Y --> Z[銷燬執行緒池並釋放資源] Z --> AA[主函式結束]

thread_pool.h

#ifndef _THREAD_POOL_H_
#define _THREAD_POOL_H_

#include <stdio.h>	 // 標準輸入輸出庫
#include <stdbool.h> // 布林型別庫
#include <unistd.h>	 // UNIX 標準庫,包含 sleep 函式
#include <stdlib.h>	 // 標準庫,包含 malloc 和 free 函式
#include <string.h>	 // 字串處理庫
#include <strings.h> // 字串處理庫

#include <errno.h>	 // 錯誤號庫
#include <pthread.h> // POSIX 執行緒庫

#define MAX_WAITING_TASKS 1000 // 處於等待狀態的任務數量最大為1000
#define MAX_ACTIVE_THREADS 20  // 活躍執行緒的最大數量, 但該數量最佳應該==CPU一次性可執行的執行緒數量, 例如6核12執行緒, 則為12

/*************第一步: 構建任務結構體******************/
// 任務結點  單向連結串列的節點,型別
struct task
{
    void *(*do_task)(void *arg); // 任務函式指標  指向執行緒要執行的任務  格式是固定的
    void *arg;					 // 需要傳遞給任務的引數,如果不需要,則NULL

    struct task *next; // 指向下一個任務結點的指標
};

// 執行緒池的管理結構體
typedef struct thread_pool
{
    pthread_mutex_t lock; // 互斥鎖, 用於保護任務佇列
    pthread_cond_t cond;  // 條件量, 代表任務佇列中任務個數的變化---如果主執行緒向佇列投放任務, 則可以透過條件變數來喚醒哪些睡著了的執行緒

    bool shutdown; // 是否需要銷燬執行緒池, 控制執行緒退出, 進而銷燬整個執行緒池

    struct task *task_list; // 用於儲存任務的連結串列, 任務佇列剛開始沒有任何任務, 是一個具有頭節點的空鏈佇列

    pthread_t *tids; // 用於記錄執行緒池中執行緒的ID

    unsigned max_waiting_tasks; // 執行緒池中處於等待狀態的任務數量最大值
    unsigned waiting_tasks;		// 處於等待狀態的執行緒數量
    unsigned active_threads;	// 正在活躍的執行緒數量
} thread_pool;

// 初始化執行緒池
bool init_pool(thread_pool *pool, unsigned int threads_number);

// 向執行緒池中新增任務
bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void *task);

// 先執行緒池中新增執行緒
int add_thread(thread_pool *pool, unsigned int additional_threads_number);

// 從執行緒池中刪除執行緒
int remove_thread(thread_pool *pool, unsigned int removing_threads_number);

// 銷燬執行緒池
bool destroy_pool(thread_pool *pool);

// 任務函式 執行緒例程
void *routine(void *arg);

#endif

thread_pool.c

#include "thread_pool.h" // 包含執行緒池標頭檔案

// 執行緒取消處理函式,確保執行緒取消時解鎖互斥鎖
void handler(void *arg)
{
    printf("[%u] 結束了.\n",
           (unsigned)pthread_self()); // 列印執行緒結束資訊

    pthread_mutex_unlock((pthread_mutex_t *)arg); // 解鎖互斥鎖
}

// 執行緒執行的任務函式
void *routine(void *arg)
{
    // 除錯
    #ifdef DEBUG
    printf("[%u] is started.\n",
           (unsigned)pthread_self()); // 列印執行緒開始資訊
    #endif

    // 把需要傳遞給執行緒任務的引數進行備份
    thread_pool *pool = (thread_pool *)arg; // 將傳入的引數轉換為執行緒池指標
    struct task *p;							// 定義一個任務指標

    while (1) // 無限迴圈,持續處理任務
    {
        /*
		** push a cleanup functon handler(), make sure that
		** the calling thread will release the mutex properly
		** even if it is cancelled during holding the mutex.
		**
		** NOTE:
		** pthread_cleanup_push() is a macro which includes a
		** loop in it, so if the specified field of codes that
		** paired within pthread_cleanup_push() and pthread_
		** cleanup_pop() use 'break' may NOT break out of the
		** truely loop but break out of these two macros.
		** see line 61 below.
		*/
        /*
		 * 注意:
		 * 推送一個清理函式handler(),確保呼叫執行緒將正確釋放互斥量,即使它在持有互斥量期間被取消。
		 *
		 * pthread_cleanup_push()是一個宏,其中包含一個迴圈,
		 * 所以如果在pthread_cleanup_push()和pthread_ cleanup_pop()中配對的程式碼的指定欄位使用` break `可能不會跳出真正的迴圈,
		 * 而是跳出這兩個宏。參見下面的第61行。
		 */
        //================================================//
        pthread_cleanup_push(handler, (void *)&pool->lock); // 註冊取消處理函式
        pthread_mutex_lock(&pool->lock);					// 加鎖,保護共享資源
        //================================================//

        // 1,如果沒有任務且執行緒池未關閉,則等待
        while (pool->waiting_tasks == 0 && !pool->shutdown)
        {
            pthread_cond_wait(&pool->cond, &pool->lock); // 等待條件變數
        }

        // 2,  如果沒有任務且執行緒池已關閉,則退出
        if (pool->waiting_tasks == 0 && pool->shutdown == true)
        {
            pthread_mutex_unlock(&pool->lock); // 解鎖
            pthread_exit(NULL);				   // CANNOT use 'break';  退出執行緒
        }

        // 3,    有任務則取出任務
        p = pool->task_list->next;		 // 獲取第一個任務
        pool->task_list->next = p->next; // 將任務從連結串列中移除
        pool->waiting_tasks--;			 // 減少等待任務計數

        //================================================//
        pthread_mutex_unlock(&pool->lock); // 解鎖
        pthread_cleanup_pop(0);			   // 取消註冊的取消處理函式
        //================================================//

        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); // 禁止執行緒取消
        (p->do_task)(p->arg);								  // 執行任務
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);  // 允許執行緒取消

        free(p); // 釋放任務記憶體
    }

    pthread_exit(NULL); // 退出執行緒
}

// 初始化執行緒池 pool執行緒池指標  threads_number 初始化執行緒的個數
bool init_pool(thread_pool *pool, unsigned int threads_number)
{
    // 初始化互斥鎖
    pthread_mutex_init(&pool->lock, NULL);

    // 初始化條件量
    pthread_cond_init(&pool->cond, NULL);

    // 銷燬標誌 設定執行緒池為未關閉狀態
    pool->shutdown = false; // 不銷燬

    // 給任務連結串列的節點申請堆記憶體
    pool->task_list = malloc(sizeof(struct task));

    // 申請堆記憶體,用於儲存建立出來的執行緒的ID
    pool->tids = malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS);

    // 錯誤處理,對malloc進行錯誤處理
    if (pool->task_list == NULL || pool->tids == NULL)
    {
        perror("分配記憶體錯誤");
        return false;
    }

    // 對任務連結串列中的節點的指標域進行初始化
    pool->task_list->next = NULL;

    // 設定執行緒池中處於等待狀態的任務數量最大值
    pool->max_waiting_tasks = MAX_WAITING_TASKS;

    // 設定等待執行緒處理的任務的數量為0,說明現在沒有任務
    pool->waiting_tasks = 0;

    // 設定執行緒池中活躍的執行緒的數量
    pool->active_threads = threads_number;

    int i;

    // 迴圈建立活躍執行緒
    for (i = 0; i < pool->active_threads; i++)
    {
        // 建立執行緒  把執行緒的ID儲存在申請的堆記憶體
        if (pthread_create(&((pool->tids)[i]), NULL,
                           routine, (void *)pool) != 0)
        {
            perror("建立執行緒錯誤");
            return false;
        }

        // 用於除錯
        #ifdef DEBUG
        printf("[%u]:[%s] ==> tids[%d]: [%u] is created.\n",
               (unsigned)pthread_self(), __FUNCTION__,
               i, (unsigned)pool->tids[i]); // 列印執行緒建立資訊
        #endif
    }

    return true;
}

// 向執行緒池的任務連結串列中新增任務
bool add_task(thread_pool *pool,
              void *(*do_task)(void *arg), void *arg)
{
    // 給任務連結串列節點申請記憶體
    struct task *new_task = malloc(sizeof(struct task));
    if (new_task == NULL) // 檢查記憶體分配是否成功
    {
        perror("申請記憶體錯誤");
        return false;
    }

    new_task->do_task = do_task; // 設定任務函式指標
    new_task->arg = arg;		 // 設定任務引數
    new_task->next = NULL;		 // 指標域設定為NULL  初始化任務的下一個指標

    //============ LOCK =============//
    pthread_mutex_lock(&pool->lock); // 加鎖,保護共享資源
    //===============================//

    // 說明要處理的任務的數量大於能處理的任務數量
    if (pool->waiting_tasks >= MAX_WAITING_TASKS) // 檢查等待任務是否超過最大值
    {
        pthread_mutex_unlock(&pool->lock); // 解鎖

        fprintf(stderr, "任務太多.\n"); // 列印錯誤資訊
        free(new_task);					// 釋放新任務記憶體

        return false;
    }

    struct task *tmp = pool->task_list; // 獲取任務連結串列頭

    // 遍歷連結串列,找到單向連結串列的尾節點
    while (tmp->next != NULL)
        tmp = tmp->next;

    // 把新的要處理的任務插入到連結串列的尾部  尾插
    tmp->next = new_task;

    // 要處理的任務的數量+1 (等待任務數量+1)
    pool->waiting_tasks++;

    //=========== UNLOCK ============//
    pthread_mutex_unlock(&pool->lock); // 解鎖
    //===============================//

    // 除錯
    #ifdef DEBUG
    printf("[%u][%s] ==> a new task has been added.\n",
           (unsigned)pthread_self(), __FUNCTION__); // 列印任務新增資訊
    #endif

    // 喚醒第一個處於阻塞佇列中的執行緒
    pthread_cond_signal(&pool->cond);
    return true;
}

// 向執行緒池加入新執行緒
int add_thread(thread_pool *pool, unsigned additional_threads)
{
    // 判斷需要新增的新執行緒的數量是否為0  如果沒有要新增的執行緒,直接返回
    if (additional_threads == 0)
        return 0;

    // 計算執行緒池中匯流排程的數量
    unsigned total_threads =
        pool->active_threads + additional_threads;

    int i, actual_increment = 0; // 初始化計數器

    // 迴圈建立新執行緒
    for (i = pool->active_threads; i < total_threads && i < MAX_ACTIVE_THREADS; i++)
    {
        // 建立新執行緒
        if (pthread_create(&((pool->tids)[i]),
                           NULL, routine, (void *)pool) != 0)
        {
            perror("增加活躍執行緒錯誤"); // 列印錯誤資訊

            // 如果沒有成功建立任何執行緒,返回錯誤
            if (actual_increment == 0)
                return -1;

            break; // 退出迴圈
        }
        actual_increment++; // 增加計數器

        #ifdef DEBUG
        printf("[%u]:[%s] ==> tids[%d]: [%u] is created.\n",
               (unsigned)pthread_self(), __FUNCTION__,
               i, (unsigned)pool->tids[i]); // 列印執行緒建立資訊
        #endif
    }

    // 記錄此時執行緒池中活躍執行緒的總數
    pool->active_threads += actual_increment; // 更新活躍執行緒數
    return actual_increment;				  // 返回實際增加的執行緒數
}
// 從執行緒池中刪除執行緒
int remove_thread(thread_pool *pool, unsigned int removing_threads)
{
    if (removing_threads == 0)
        return pool->active_threads; // 如果沒有要刪除的執行緒,直接返回

    int remaining_threads = pool->active_threads - removing_threads;   // 計算剩餘執行緒數
    remaining_threads = remaining_threads > 0 ? remaining_threads : 1; // 確保至少有一個執行緒

    int i;
    for (i = pool->active_threads - 1; i > remaining_threads - 1; i--) // 迴圈取消執行緒
    {
        errno = pthread_cancel(pool->tids[i]); // 取消執行緒

        if (errno != 0) // 檢查取消是否成功
            break;

        #ifdef DEBUG
        printf("[%u]:[%s] ==> cancelling tids[%d]: [%u]...\n",
               (unsigned)pthread_self(), __FUNCTION__,
               i, (unsigned)pool->tids[i]); // 列印執行緒取消資訊
        #endif
    }

    if (i == pool->active_threads - 1) // 如果沒有成功取消任何執行緒,返回錯誤
        return -1;
    else
    {
        pool->active_threads = i + 1; // 更新活躍執行緒數
        return i + 1;				  // 返回剩餘執行緒數
    }
}
// 銷燬執行緒池
bool destroy_pool(thread_pool *pool)
{
    // 1,啟用所有執行緒 設定關閉標誌
    pool->shutdown = true;
    pthread_cond_broadcast(&pool->cond); // 喚醒所有等待中的執行緒

    // 2, 等待執行緒們執行完畢
    int i;
    for (i = 0; i < pool->active_threads; i++) // 迴圈等待所有執行緒退出
    {
        /**
		 * pthread_join(pool->tids[i], NULL) 的作用是等待執行緒池中第 i 個執行緒終止,並清理其相關資源。透過這種方式,可以確保在銷燬執行緒池時,所有執行緒都已經安全地終止。
		 * pthread_join 是 POSIX 執行緒庫中的一個函式,用於等待一個執行緒的終止。它的功能類似於程序中的 wait 系統呼叫。
		 */
        errno = pthread_join(pool->tids[i], NULL); // 等待執行緒退出
        if (errno != 0)							   // 檢查等待是否成功
        {
            printf("join tids[%d] error: %s\n",
                   i, strerror(errno)); // 列印錯誤資訊
        }
        else
            printf("[%u] is joined\n", (unsigned)pool->tids[i]); // 列印執行緒退出資訊
    }

    // 3, 銷燬執行緒池
    free(pool->task_list); // 釋放任務連結串列記憶體
    free(pool->tids);	   // 釋放執行緒ID陣列記憶體
    free(pool);			   // 釋放執行緒池結構體記憶體

    return true;
}

執行緒執行的任務函式流程圖

void *routine(void *arg)

mermaid

graph TD A[執行緒執行的任務函式開始] --> B[註冊取消處理函式] B --> C[加鎖] C --> D{是否有任務 且 執行緒池未關閉?} D -- 否 --> E[等待條件變數] D -- 是 --> F{是否沒有任務 且 執行緒池已關閉?} F -- 是 --> G[解鎖並退出執行緒] F -- 否 --> H[取出任務] H --> I[從連結串列中移除任務] I --> J[減少等待任務計數] J --> K[解鎖] K --> L[取消註冊的取消處理函式] L --> M[禁止執行緒取消] M --> N[執行任務] N --> O[允許執行緒取消] O --> P[釋放任務記憶體] P --> A

銷燬執行緒池流程圖

mermaid

graph TD A[銷燬執行緒池] --> B[設定關閉標誌] B --> C[喚醒所有等待執行緒] C --> D[等待所有執行緒終止] D --> E[釋放任務連結串列記憶體] E --> F[釋放執行緒ID陣列記憶體] F --> G[釋放執行緒池結構體記憶體] G --> H[執行緒池銷燬完成]

參考

  • Hexo (smartyue076.github.io)
  • 執行緒池原理與實現_嗶哩嗶哩_bilibili
  • Linux環境程式設計圖文指南(配影片教程) (豆瓣) (douban.com)
  • Linux高效能伺服器程式設計 (豆瓣) (douban.com)

相關文章