Linux雜談: 實現一種簡單實用的執行緒池(C語言)

TpCode發表於2020-11-17

基本功能

1. 實現一個執行緒的佇列,佇列中的執行緒啟動後不再釋放;

2. 沒有任務執行時,執行緒處於pending狀態,等待喚醒,不佔cpu;

3. 當有任務需要執行時,從執行緒佇列中取出一個執行緒執行任務;

4. 任務執行完成後執行緒再次進入pending狀態,等待喚醒;

 

擴充套件功能

1. 執行緒的佇列大小可設定;

2. 最大可建立的執行緒數可設定;

3. 根據執行需求,按需步進啟動執行緒,避免大量執行緒一直處於pending狀態,佔用資源;

 

關鍵程式碼分析

資料結構

 1 /* 執行緒執行的任務引數 */
 2 typedef struct
 3 {
 4     void (*func)(void*, void*);    /* 任務函式指標 */
 5     void *arg1;                    /* 任務函式第一個引數 */
 6     void *arg2;                    /* 任務函式第二個引數 */
 7 }tThreadTaskInfo;
 8 
 9 /* 執行緒池引數 */
10 typedef struct
11 {
12     pthread_mutex_t lock;          /* 執行緒池互斥鎖 */
13     pthread_cond_t cond;           /* 執行緒池同步訊號 */
14 
15     pthread_t *threads;            /* 儲存執行緒池建立的所有執行緒 */
16     int32_t threadMaxNum;          /* 最大可建立執行緒數 */
17     int32_t threadStartStep;       /* 一次啟動執行緒的個數 */
18     int32_t threadStartCnt;        /* 已啟動執行緒個數 */
19     int32_t threadPendCnt;         /* 已啟動但是處於Pending狀態的執行緒 */
20 
21     tThreadTaskInfo *taskQueue;    /* 等待執行的任務佇列 */
22     int32_t taskQueueSize;         /* 任務佇列的大小 */
23     int32_t taskQueueHead;         /* 當前任務佇列頭索引 */
24     int32_t taskQueueTail;         /* 當前任務佇列尾索引 */
25     int32_t taskPendCnt;           /* 等待執行的任務個數 */
26 
27     int32_t isShutdown;            /* 執行緒池正在關閉 */
28 }tThreadpoolInfo;

 

建立執行緒池

  • 建立執行緒池時只分配了儲存pthread_t的空間,但是不啟動執行緒,後面根據需求步進啟動;
 1 /************************************
 2  * 建立執行緒池
 3  *
 4  * @threadMaxNum     -- 最大可建立執行緒個數
 5  * @threadStartStep  -- 一次啟動執行緒的個數
 6  * @taskQueueSize    -- 任務佇列的大小
 7  *
 8  * @Retuen  --  成功:執行緒池的引用
 9  *              失敗:NULL
10  * **********************************/
11 tThreadpoolInfo* threadpool_create(
12     int32_t threadMaxNum,
13     int32_t threadStartStep,
14     int32_t taskQueueSize)
15 {
16     tThreadpoolInfo *threadpool = NULL;
17 
18     if ((0 >= threadMaxNum)
19         || (0 >= threadStartStep)
20         || (0 >= taskQueueSize))
21     {
22         THREADPOOL_ERR("invalid param.\r\n");
23         goto error_exit;
24     }
25 
26     threadpool = (tThreadpoolInfo *)malloc(sizeof(tThreadpoolInfo));
27     if (NULL == threadpool)
28     {
29         THREADPOOL_ERR("malloc threadpool failed.\r\n");
30         goto error_exit;
31     }
32 
33     memset(threadpool, 0, sizeof(tThreadpoolInfo));
34     threadpool->threadMaxNum = threadMaxNum;
35     threadpool->threadStartStep = threadStartStep;
36     threadpool->taskQueueSize = taskQueueSize;
37 
38     /* 分配執行緒儲存資源 */
39     threadpool->threads = (pthread_t *)calloc(threadMaxNum, sizeof(pthread_t));
40     if (NULL == threadpool->threads)
41     {
42         THREADPOOL_ERR("malloc threads failed.\r\n");
43         goto error_exit;
44     }
45 
46     /* 分配任務佇列 */
47     threadpool->taskQueue = (tThreadTaskInfo *)calloc(taskQueueSize, sizeof(tThreadTaskInfo));
48     if (NULL == threadpool->taskQueue)
49     {
50         THREADPOOL_ERR("malloc task queue failed.\r\n");
51         goto error_exit;
52     }
53 
54     /* 初始化互斥訊號量和同步訊號 */
55     if (0 != THREADPOOL_LOCK_INIT(threadpool))
56     {
57         THREADPOOL_ERR("mutex init failed.\r\n");
58         goto error_exit;
59     }
60 
61     if (0 != THREADPOOL_COND_INIT(threadpool))
62     {
63         THREADPOOL_ERR("cond init failed.\r\n");
64         goto error_exit;
65     }
66 
67     return threadpool;
68 
69 error_exit:
70 
71     if (threadpool != NULL)
72     {
73         threadpool_free(threadpool);
74     }
75 
76     return NULL;
77 }

 

向執行緒池新增任務

  • 檢視等待佇列是否有空閒,如果沒有空閒則返回錯誤;
  • 檢視當前有沒有處於pending的執行緒,如果沒有則按照步進啟動新的執行緒,如果已達到最大執行緒數則返回錯誤;
  • 將任務新增到佇列中,並喚醒一個執行緒執行任務;
 1 /************************************
 2  * 向執行緒池新增任務
 3  *
 4  * @threadpool -- 執行緒池引用
 5  * @taskfunc   -- 任務回撥函式
 6  * @arg1       -- 任務第一個引數
 7  * @arg1       -- 任務第二個引數
 8  *
 9  * @Return  --  成功: 0
10  *              失敗: -1
11  * **********************************/
12 int32_t threadpool_addtask(
13     tThreadpoolInfo *threadpool,
14     THREADPOOLTASKFUNC taskfunc,
15     void *arg1,
16     void *arg2)
17 {
18     int32_t ret = 0;
19 
20     if ((NULL == threadpool) || (NULL == taskfunc))
21     {
22         THREADPOOL_ERR("invalid param.\r\n");
23         return -1;
24     }
25 
26     THREADPOOL_LOCK(threadpool);
27 
28     do
29     {
30         if (threadpool->isShutdown)
31         {
32             THREADPOOL_ERR("threadpool is shutdown.\r\n");
33             ret = -1;
34             break;
35         }
36 
37         /* 判斷等待執行的任務佇列是否滿 */
38         if (threadpool->taskPendCnt == threadpool->taskQueueSize)
39         {
40             THREADPOOL_ERR("task queue is full.\r\n");
41             ret = -1;
42             break;
43         }
44 
45         /* 如果pending狀態的執行緒已用完,則啟動新的執行緒 */
46         if (threadpool->threadPendCnt <= 0)
47         {
48             if (0 != threadpool_start(threadpool))
49             {
50                 ret = -1;
51                 break;
52             }
53         }
54 
55         /* 將任務放入對尾 */
56         threadpool->taskQueue[threadpool->taskQueueTail].func = taskfunc;
57         threadpool->taskQueue[threadpool->taskQueueTail].arg1 = arg1;
58         threadpool->taskQueue[threadpool->taskQueueTail].arg2 = arg2;
59 
60         threadpool->taskQueueTail = (threadpool->taskQueueTail + 1) % threadpool->taskQueueSize;
61         threadpool->taskPendCnt++;
62 
63         /* 喚醒一個執行緒執行任務 */
64         THREADPOOL_COND_SIGNAL(threadpool);
65 
66     } while(0);
67 
68     THREADPOOL_UNLOCK(threadpool);
69     return ret;
70 }

 

執行緒的回撥函式

  • 執行緒第一次啟動和被喚醒後檢查佇列中是否有需要執行的任務,如果沒有則繼續等待喚醒;
  • 如果有需要執行的任務,則從佇列中取一個任務並執行;
  • 如果執行緒池已銷燬,則退出執行緒;
 1 /************************************
 2  * 執行緒回撥函式
 3  * 等待執行緒池分配任務並執行分配的任務
 4  *
 5  * @arg  -- 執行緒池引用
 6  * **********************************/
 7 void* thread_callback(void *arg)
 8 {
 9     tThreadpoolInfo *threadpool = (tThreadpoolInfo *)arg;
10     tThreadTaskInfo task;
11 
12     while (1)
13     {
14         THREADPOOL_LOCK(threadpool);
15 
16         /* 等待任務分配的訊號 
17          * 如果當前沒有等待執行的任務,並且執行緒池沒有關閉則繼續等待訊號 */
18         while ((0 == threadpool->taskPendCnt)
19                 && (0 == threadpool->isShutdown))
20         {
21             THREADPOOL_COND_WAIT(threadpool);
22         }
23 
24         /* 如果執行緒池已關閉,則退出執行緒  */
25         if (threadpool->isShutdown)
26             break;
27 
28         /* 取任務佇列中當前第一個任務 */
29         task.func = threadpool->taskQueue[threadpool->taskQueueHead].func;
30         task.arg1 = threadpool->taskQueue[threadpool->taskQueueHead].arg1;
31         task.arg2 = threadpool->taskQueue[threadpool->taskQueueHead].arg2;
32 
33         threadpool->taskQueueHead = (threadpool->taskQueueHead + 1) % threadpool->taskQueueSize;
34         threadpool->taskPendCnt--;
35         threadpool->threadPendCnt--;
36 
37         THREADPOOL_UNLOCK(threadpool);
38 
39         /* 執行任務 */
40         (*(task.func))(task.arg1, task.arg2);
41 
42         /* 任務執行完成後,執行緒進入pending狀態 */
43         THREADPOOL_LOCK(threadpool);
44         threadpool->threadPendCnt++;
45         THREADPOOL_UNLOCK(threadpool);
46     }
47 
48     threadpool->threadStartCnt--;
49     THREADPOOL_UNLOCK(threadpool);
50 
51     pthread_exit(NULL);
52 }

 

執行緒池銷燬

  • 銷燬為確保資源釋放,需要喚醒所有執行緒,並等待所有執行緒退出;
 1 /************************************
 2  * 刪除執行緒池
 3  *
 4  * @threadpool  -- 執行緒池引用
 5  * **********************************/
 6 int32_t threadpool_destroy(tThreadpoolInfo *threadpool)
 7 {
 8     int32_t ret = 0;
 9     int32_t i = 0;
10 
11     if (NULL == threadpool)
12     {
13         THREADPOOL_ERR("invalid param.\r\n");
14         return -1;
15     }
16 
17     THREADPOOL_LOCK(threadpool);
18 
19     do
20     {
21         if (threadpool->isShutdown)
22         {
23             THREADPOOL_UNLOCK(threadpool);
24             break;
25         }
26 
27         threadpool->isShutdown = 1;
28 
29         /* 喚醒所有執行緒 */
30         if (0 != THREADPOOL_COND_BROADCAST(threadpool))
31         {
32             THREADPOOL_ERR("cond broadcast failed.\r\n");
33             threadpool->isShutdown = 0;
34             continue;
35         }
36 
37         THREADPOOL_UNLOCK(threadpool);
38 
39         /* 等待所有程式退出 */
40         for (i = 0; i < threadpool->threadStartCnt; i++)
41         {
42             pthread_cancel(threadpool->threads[i]);
43             pthread_join(threadpool->threads[i], NULL);
44         }
45 
46     }while(0);
47 
48     if (0 != ret)
49     {
50         threadpool->isShutdown = 0;
51         return ret;
52     }
53 
54     threadpool_free(threadpool);
55     return ret;
56 }

 

執行緒池測試

  • 建立最大執行緒數=256,佇列大小=64,啟動步進=8 的執行緒池;
  • 向執行緒池新增1024個任務,如果新增失敗則等待1秒再新增;
  • 驗證1024個任務是否均能執行;
 1 /***********************************
 2  * Filename : test_main.c
 3  * Author :   taopeng
 4  * *********************************/
 5 
 6 #include <stdio.h>
 7 #include <stdlib.h>
 8 #include <string.h>
 9 #include <unistd.h>
10 
11 #include "threadpool.h"
12 
13 void test_task(void *arg)
14 {
15     long id = (long)arg;
16 
17     printf("task[%ld] enter\r\n", id);
18     sleep(3);
19 
20     return;
21 }
22 
23 int32_t main(int32_t argc, char *argv[])
24 {
25     tThreadpoolInfo *threadpool;
26     long id;
27 
28     threadpool = threadpool_create(128, 8, 64);
29     if (NULL == threadpool)
30         return -1;
31 
32     for (id = 1; id <= 1024;)
33     {
34         if (0 != threadpool_addtask(threadpool, (THREADPOOLTASKFUNC)test_task, (void *)id, NULL))
35         {
36             sleep(1);
37             continue;
38         }
39 
40         id++;
41     }
42 
43     sleep(30);
44 
45     threadpool_destroy(threadpool);
46     return 0;
47 }

 

程式碼例項連結

https://gitee.com/github-18274965/threadpool.git

 

相關文章