執行緒池的實現原始碼及應用舉例

Rice_rice發表於2024-06-02

1.執行緒池本質

​ 多個執行緒組成的一個集合,目的為了併發執行任務,定義時是一個結構體,成員有互斥鎖,條件變數,任務鏈佇列指標,任務鏈佇列中等待的任務個數,當前活躍的執行緒數量,執行緒ID,執行緒銷燬標記

2.執行緒池的關鍵技術
(1)萬能函式指標(通用函式指標): *void *(*p)(void )
(使用技巧:函式的引數個數超過1個時,引數可以打包成結構體,多個引數就變成一個引數(全部包含在結構體裡面了))

原理:該函式需要用到互斥鎖在完成一個任務後減少任務數量,解鎖後繼續下一個任務,還要用到條件變數在任務全部完成時透過判斷任務數量和結束標誌位退出。

(2)封裝執行緒池有關的介面函式
三大基本函式需要我們去封裝
第一個:初始化執行緒池
原理:透過對執行緒池結構體中的成員初始化讓執行緒池進入工作模式,隨後使用迴圈建立對應數量的執行緒

第二個:新增任務

原理:透過動態分配記憶體準備新的記憶體空間分配給新的任務,在新增任務時利用互斥鎖上鎖防止任務函式完成任務時減少任務數量帶來的衝中突,將任務尾插到任務連結串列中,並目對執行緒池結構體中各個成員變數進行更新。

第三個:執行緒池的銷燬(回收執行緒,想辦法讓執行緒的任務函式退出)
原理:透過改變執行緒池結構體成員變數中的結束標誌位,令所有執行緒能夠退出,隨後在任務函式中開始退出執行緒,並在這個執行緒池銷燬函式中回收所有執行緒。

執行緒池例項原始碼列舉如下:


標頭檔案(thread_pool.h)

#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <pthread.h>

#define MAX_WAITING_TASKS 1000 // 處於等待狀態的執行緒數量最大為1000
#define MAX_ACTIVE_THREADS 20  // 活躍的執行緒數量

// 任務結點  單向連結串列的節點,型別
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;
	/*是否需要銷燬執行緒池,用於指示執行緒池是否處於銷燬狀態。它的作用是線上程池需要被銷燬時,向執行緒池中的工作執行緒發出訊號,告知它們停止接受新的任務,並逐漸退出。具體來說,當 shutdown 標記被設定為 true 時,執行緒池將不再接受新的任務提交,但會繼續執行已經提交的任務,直到所有任務都執行完畢。一旦執行緒池中的任務執行完畢,工作執行緒就會逐個退出,釋放相關資源,最終銷燬整個執行緒池。*/

	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);

介面函式的實現原始碼(thread_pool.c):

#include "thread_pool.h"

/*
 *	@name   : handler
 *	@brief  : 接到取消請求之後進行釋放互斥鎖
 *	@params :
 *	          @arg : 傳入每個新增的任務隨機的10秒內的秒數
 *	@retval : NULL
 * 	@version:
 * 	@note   :
 */
void handler(void *arg)
{
	printf("[%u] is ended.\n",
		   (unsigned)pthread_self()); // 響應取消請求之後自動處理的例程:釋放互斥鎖,以確保不會因為執行緒被取消而導致資源洩漏或死鎖等問題。
	pthread_mutex_unlock((pthread_mutex_t *)arg);
}

/*
 *	@name   : routine
 *	@brief  : 接到取消請求之後進行釋放互斥鎖
 *	@params :
 *	          @arg : 傳遞給執行緒任務的引數
 *	@retval : NULL
 * 	@version:
 * 	@note   :
 */
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)
	{
		/*
		pthread_cleanup_push()是一個宏,用於向執行緒的取消處理器棧中註冊(也可以簡單理解為繫結)一個處理函式。作用是線上程退出時自動執行註冊的處理函式,即handler
		*/
		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--;
		// 4, 釋放互斥鎖,並用pthread_cleanup_pop取消在pthread_cleanup_push中註冊的清理處理器
		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); // 任務完成,釋放任務所佔用的記憶體空間
	}
}

/*
 *	@name   : init_pool
 *	@brief  : 初始化執行緒池
 *	@params :
 *	          @*pool : 執行緒池的管理結構體指標
 *	          @threads_number : 初始確定的執行緒數
 *	@retval : 成功返回true,失敗false
 * 	@version:
 * 	@note   :
 */
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));				 // 給連結串列的節點申請堆記憶體
	pool->tids = malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS); // 申請堆記憶體,用於儲存建立出來的TID
	// 錯誤處理,對malloc進行錯誤處理
	if (pool->task_list == NULL || pool->tids == NULL)
	{
		perror("allocate memory error");
		return false;
	}

	pool->task_list->next = NULL;				 // 對任務連結串列中的節點的指標域進行初始化
	pool->max_waiting_tasks = MAX_WAITING_TASKS; // 設定執行緒池中執行緒數量的最大值
	pool->waiting_tasks = 0;					 // 設定等待執行緒處理的任務的數量為0,說明現在沒有任務
	pool->active_threads = threads_number;		 // 設定執行緒池中活躍的執行緒的數量

	for (int i = 0; i < pool->active_threads; i++) // 迴圈建立活躍執行緒
	{
		// 建立執行緒  把執行緒的ID儲存在申請的堆記憶體
		if (pthread_create(&((pool->tids)[i]), NULL,
						   routine, (void *)pool) != 0)
		{
			perror("create threads error");
			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;
}

/*
 *	@name   : add_task
 *	@brief  : 向執行緒池的任務連結串列中新增任務,並喚醒
 *	@params :
 *	          @*pool : 執行緒池的管理結構體指標
 *	          @threads_number : 初始確定的執行緒數
 *	@retval : 成功返回true,失敗false
 * 	@version:
 * 	@note   :
 */
// 先執行緒池的任務連結串列中新增任務
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("allocate memory error");
		return false;
	}

	new_task->do_task = do_task;
	new_task->arg = arg;
	new_task->next = NULL; // 指標域設定為NULL

	pthread_mutex_lock(&pool->lock); // 進行執行緒池任務新增

	// 說明要處理的任務的數量大於能處理的任務數量,直接解鎖釋放資源並返回
	if (pool->waiting_tasks >= MAX_WAITING_TASKS)
	{
		pthread_mutex_unlock(&pool->lock);
		fprintf(stderr, "too many tasks.\n");
		free(new_task);
		return false;
	}

	struct task *tmp = pool->task_list; // 獲取執行緒池單連結串列的頭節點地址
	while (tmp->next != NULL)			// 遍歷連結串列,找到單向連結串列的尾節點
		tmp = tmp->next;

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

	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;
}

/*
 *	@name   : add_thread
 *	@brief  : 向執行緒池加入新執行緒
 *	@params :
 *	          @*pool : 執行緒池的管理結構體指標
 *	          @additional_threads : 需要新增的執行緒數
 *	@retval : 實際增加的執行緒數
 * 	@version:
 * 	@note   :
 */
int add_thread(thread_pool *pool, unsigned additional_threads)
{
	if (additional_threads == 0) // 判斷需要新增的新執行緒的數量是否為0,是的話直接返回
		return 0;

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

	int i, actual_increment = 0;													 // actual_increment 為實際增加的程序數
	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("add threads error");
			// no threads has been created, return fail
			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;
}
/*
 *	@name   : remove_thread
 *	@brief  : 移除多餘執行緒
 *	@params :
 *	          @*pool : 執行緒池的管理結構體指標
 *	          @*removing_threads : 需要移除的執行緒數量
 *	@retval : 移除後剩下的活躍執行緒數
 * 	@version:
 * 	@note   :
 */
int remove_thread(thread_pool *pool, unsigned int removing_threads)
{
	if (removing_threads == 0) // 判斷需要新增的新執行緒的數量是否為0,是的話直接返回
		return pool->active_threads;

	int remaining_threads = pool->active_threads - removing_threads;
	remaining_threads = remaining_threads > 0 ? remaining_threads : 1; // 初步計算經過移除後,剩下的執行緒數,並判斷;最終剩餘的執行緒數不得小於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) // 若沒有移除成功,則返回-1
		return -1;
	else
	{
		pool->active_threads = i + 1;// 否則返回活躍的執行緒數
		return i + 1;
	}
}

/*
 *	@name   : destroy_pool
 *	@brief  : 銷燬執行緒池
 *	@params :
 *	          @*pool : 執行緒池的管理結構體指標
 *	@retval : 成功返回true,失敗false
 * 	@version:
 * 	@note   :
 */
bool destroy_pool(thread_pool *pool)
{
	pool->shutdown = true; // 1, 修改執行緒池結構體的成員,並通知所有執行緒
	pthread_cond_broadcast(&pool->cond);

	for (int i = 0; i < pool->active_threads; i++) // 2, 等待所有執行緒退出,並回收資源
	{
		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]);
	}

	free(pool->task_list); // 釋放申請了的堆記憶體
	free(pool->tids);
	free(pool);

	return true;
}

應用舉例(main.c):

#include "thread_pool.h"
/**
 * @file name:	--
 * @brief
 * @author ni456xinmie@163.com
 * @date 2024/04/25
 * @version 1.0 :版本
 * @property :
 * @note
 * CopyRight (c)  2023-2024   ni456xinmie@163.com   All Right Reseverd
 */

/*
 *	@name   : mytask
 *	@brief  : 給每個執行緒安排的具體任務
 *	@params :
 *	          @arg : 傳入每個新增的任務隨機的10秒內的秒數
 *	@retval : NULL
 * 	@version:
 * 	@note   :
 * 			  1.__FUNCTION__ 是 C 和 C++ 語言中的預定義宏,用於獲取當前所在函式的名稱(在 C++ 中,也包括成員函式)。它會在編譯時被替換為當前函式的字串字面值。
 * 			  2.這部分可以換成自己需要安排的程序任務
 */
void *mytask(void *arg)
{
	int n = (int)arg; // 定義整型變數接收引數

	printf("[%u][%s] ==> job will be done in %d sec...\n",
		   (unsigned)pthread_self(), __FUNCTION__, n);
	sleep(n);
	printf("[%u][%s] ==> job done!\n",
		   (unsigned)pthread_self(), __FUNCTION__);

	return NULL;
}

/*
 *	@name   : count_time
 *	@	    : 計時器,每隔一秒輸出當前秒數
 *	@params :
 *	          @*arg : NULL
 *	@retval : NULL
 * 	@version:
 * 	@note   :
 */
void *count_time(void *arg)
{
	int i = 0;
	while (1)
	{
		sleep(1);
		printf("sec: %d\n", ++i);
	}
}

int main()
{
	// 建立執行緒進行實時輸出時間
	pthread_t a;
	pthread_create(&a, NULL, count_time, NULL);

	// 1, initialize the pool 初始化帶有2條執行緒的執行緒池
	thread_pool *pool = malloc(sizeof(thread_pool));
	init_pool(pool, 2);

	// 2, throw tasks  投入3個任務
	printf("throwing 3 tasks...\n");
	add_task(pool, mytask, (void *)(rand() % 10));
	add_task(pool, mytask, (void *)(rand() % 10));
	add_task(pool, mytask, (void *)(rand() % 10));

	// 3, check active threads number 顯示當前的執行緒數量
	printf("current thread number: %d\n",
		   remove_thread(pool, 0));
	sleep(9);

	// 4, throw tasks 投入2個任務
	printf("throwing another 2 tasks...\n");
	add_task(pool, mytask, (void *)(rand() % 10));
	add_task(pool, mytask, (void *)(rand() % 10));
	// 5, add threads 增加2條執行緒
	add_thread(pool, 2);
	sleep(5);
	// 6, remove threads 移除3條執行緒
	printf("remove 3 threads from the pool, "
		   "current thread number: %d\n",
		   remove_thread(pool, 3));

	// 7, destroy the pool 銷燬執行緒池
	destroy_pool(pool);
	return 0;
}

相關文章