C/C++ 线程池简单原理与实现

一、总述

线程池是用于解决多线程项目中的效率问题的,因此本文将从多线程入手,引出线程池的需求与作用,从而分析其原理与实现。

二、多线程的使用

如若现在出现以下场景:多个客户端同时访问服务器时,若服务器对所有客户端以串行的方式进行统筹响应,则将会大大降低业务效率。
即:当某个程序需要同时进行多个操作(如io操作)时,便可以考虑使用多线程的并行思路进行解决。
但线程是需要占用系统资源的,当所需线程过多,如百万并发服务器时,是无法分配百万线程资源的,那么该如何解决该问题呢?

三、线程池

1. 池式组件

所谓池式组件就是通过*“提前向系统申请一些过量资源,并将资源交给自定义算法在软件内部进行分配”*的一种高性能组件。
作为中间件,池式组件将会对上层业务/程序/逻辑透明,即是否存在都不影响使用,但会影响使用时的性能与稳定性等。
而线程池则是针对多线程问题提出的一种池式组件,我们将在线程池中提前申请一些过量线程后,向线程池提交我们想要实现的任务,由线程池内部管理,将任务交给特定线程进行执行。

2. 线程池作用

  1. 避免线程过多,使得资源耗尽。
  2. 减少创建与销毁线程所消耗的代价。
  3. 任务与执行进行分离(如io操作速度效率差别大时,则执行将在线程池内部进行)

3. 简单原理

3.1 组成

  1. 首先需要向系统申请过量的线程,这些线程将会在我们的管理下执行我们提交给他的具体任务,称为执行线程(队列)。
  2. 为了对申请的线程进行管理,我们仍需要一个管理组件对上述执行线程队列进行管理,分配任务,保证任务与线程一一对应,因此至少应当包含一个锁(多线程锁的作用见链接: C/C++ 多线程锁的比较及CAS实现)。
  3. 显而易见,我们还需要一个任务队列,外界能够向其中添加任务,管理组件能够从中获取任务并交给执行线程进行执行。

组成一个线程池的核心包括:执行线程队列,管理组件,任务队列

3.2 接口

线程池应当包含接口如下:

  1. 线程池的创建接口
  2. 线程池的销毁接口
  3. 向线程池中添加任务
  4. 执行线程中的工作流程

4. 简单实现

线程池的工作逻辑为:管理组件管理着执行线程队列执行任务。
因此,下面将从基础开始,即以任务队列,执行线程队列,管理组件的顺序进行实现。

4.1 组成实现

4.1.1 任务队列
typedef void (*callback)(void*);
struct nTask{
	callback func;		// 函数地址
	void* arg;			// 函数参数
	// 链表结构
	struct nTask *next;
	struct nTask *prev;
};
4.1.2 执行线程队列

可以预知的是执行线程至少需要访问manager的锁资源

struct nWorker{
	pthread_t threadid;
	int terminal;		// 线程退出标识
	struct Manager* manager;	// 需要访问manager资源

	struct nWorker* next;
	struct nWorker* prev;
};
4.1.3 链表操作(增,删)

链表操作将以define的方式进行实现

#define LIST_INSERT(item, list) do {	\
	item->prev = NULL;					\
	item->next = list;					\
	if ((list) != NULL) (list)->prev = item; \
	(list) = item;						\
} while(0)


#define LIST_REMOVE(item, list) do {	\
	if (item->prev != NULL) item->prev->next = item->next; \
	if (item->next != NULL) item->next->prev = item->prev; \
	if (list == item) list = item->next; 					\
	item->prev = item->next = NULL;							\
} while(0)
4.1.4 管理组件

管理组件管理的内容便是任务队列与执行线程队列。
为了保证多线程过程中的临界资源安全,以及解决线程同步问题,需要使用互斥锁与条件变量。

typedef struct Manager {
	struct nTask *tasks;		// 任务队列
	struct nWorker *workers;	// 执行线程队列

	pthread_mutex_t mutex;		// 互斥锁
	pthread_cond_t cond;		// 条件变量
} ThreadPool;

其中,管理组件+任务队列+执行线程队列=简易线程池

4.2 接口实现

4.2.1 线程池创建
// 初始化的任务即为对Manager结构体中的各变量进行初始化
// 包括:cond,mutex, 各workers
int nThreadPoolCreate(ThreadPool *pool, int numWorkers) {
	// 保证输入参数安全性
	if (pool == NULL) return -1;
	if (numWorkers < 1) numWorkers = 1;
	memset(pool, 0, sizeof(ThreadPool));
	
	// 参数初始化
	pthread_cond_t blank_cond = PTHREAD_COND_INITIALIZER;
	memcpy(&pool->cond, &blank_cond, sizeof(pthread_cond_t));
	
	pthread_mutex_t blank_mutex = PTHREAD_MUTEX_INITIALIZER;
	memcpy(&pool->mutex, &blank_mutex, sizeof(pthread_mutex_t));
	
	// 各workers分别初始化
	int i = 0;
	for (i = 0;i < numWorkers;i ++) {
		struct nWorker *worker = (struct nWorker*)malloc(sizeof(struct nWorker));
		if (worker == NULL) {
			perror("malloc");
			return -2;
		}
		memset(worker, 0, sizeof(struct nWorker));
		worker->manager = pool; 

		int ret = pthread_create(&worker->threadid, NULL, nThreadPoolCallback, worker);
		if (ret) {
			perror("pthread_create");
			free(worker);
			return -3;
		}
		
		LIST_INSERT(worker, pool->workers);
	}

	return 0; 
}
4.2.2 线程池销毁
// 销毁的工作即使各线程停止工作
int nThreadPoolDestory(ThreadPool *pool, int nWorker) {

	struct nWorker *worker = NULL;

	// 置位各worker停止标志位
	for (worker = pool->workers;worker != NULL;worker = worker->next) {
		worker->terminate;
	}

	pthread_mutex_lock(&pool->mutex);
	// 各worker同步关闭
	pthread_cond_broadcast(&pool->cond);

	pthread_mutex_unlock(&pool->mutex);

	pool->workers = NULL;
	pool->tasks = NULL;

	return 0;
}
4.2.3 执行线程的工作流程
// 获取任务并执行
static void *nThreadPoolCallback(void *arg) {

	struct nWorker *worker = (struct nWorker*)arg;

	while (1) {
		// 访问临界资源需上锁
		pthread_mutex_lock(&worker->manager->mutex);
		while (worker->manager->tasks == NULL) {
			if (worker->terminate) break;
			// 条件变量保证线程同步
			pthread_cond_wait(&worker->manager->cond, &worker->manager->mutex);
		}
		// 若需要退出线程,则线程结束
		if (worker->terminate) {
			pthread_mutex_unlock(&worker->manager->mutex);
			break;
		}

		struct nTask *task = worker->manager->tasks;
		LIST_REMOVE(task, worker->manager->tasks);

		pthread_mutex_unlock(&worker->manager->mutex);
		// 任务执行
		task->func(task->arg);
		free(task);
	}

	free(worker);
}
4.2.4 向线程池中添加任务
int nThreadPoolPushTask(ThreadPool *pool, struct nTask *task) {

	pthread_mutex_lock(&pool->mutex);

	LIST_INSERT(task, pool->tasks);

	pthread_cond_signal(&pool->cond);

	pthread_mutex_unlock(&pool->mutex);

}

好了,到这里一个简单的线程池即可完成了,xdm可以自行编写main函数进行测试。

如果觉得有帮助可以点一下赞,小编未来也可能会更新一些学习到的C++知识,感兴趣的可以关注一手,共同讨论学习进步。

  • 30
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: C语言线程池的代码实现可以在GitHub上找到很多开源项目,以下是其中一个例子: https://github.com/rxi/dyad 这是一个简单C语言线程池实现,主要使用了POSIX线程库,代码非常简洁和易于理解。它包含一个pool结构体,用于管理线程池的状态和任务队列等信息。主要函数包括pool_init用于初始化线程池,pool_submit用于提交任务,pool_wait用于等待线程池执行完所有任务,pool_destroy用于销毁线程池。 使用这个线程池框架,只需要简单地定义一个任务函数,并通过pool_submit提交任务,即可由线程池中的线程来执行任务。线程池内部会自动调度任务,并根据设置的线程池大小控制并发执行的线程数。 这个线程池实现还提供了一些额外的功能,例如支持任务超时设置,可以在任务执行的一定时间内获取任务的返回结果,也可以设置任务的最大重试次数。 通过在GitHub上搜索"C thread pool"关键词,还可以找到其他很多C语言线程池实现,这些开源项目提供了完整的代码实现和详细的文档说明,可以根据个人需求选择使用。 ### 回答2: 线程池是一种用于管理和复用线程的技术,通过预先创建一组线程并将其放入池中,以便在需要的时候可以重复使用。这样可以避免频繁创建和销毁线程的开销,提高系统的性能和效率。 在GitHub上有很多关于线程池实现的代码库,我以下将以Java语言为例来介绍一个常见的线程池实现。 Java的线程池是通过`ThreadPoolExecutor`类来实现的。我们可以在GitHub上搜索"ThreadPoolExecutor"关键字,就会找到很多相关的代码库。 比如,一个名为"java线程池简单实现"的代码库,这个库提供了一个简单线程池实现,包括线程池类`MyThreadPoolExecutor`和任务类`MyTask`。通过查看代码,可以了解到该线程池实现了以下几个功能: 1. 创建线程池:通过`MyThreadPoolExecutor`类的构造函数可以指定线程池的大小和其他相关的参数。 2. 提交任务:通过调用`MyThreadPoolExecutor`类的`submit()`方法将任务提交到线程池中。 3. 执行任务:线程池会自动管理和调度线程,并调用任务的`run()`方法来执行任务。 4. 监控线程池:可以通过`MyThreadPoolExecutor`类提供的方法获取线程池的状态,比如当前活动的线程数、完成的任务数等。 5. 终止线程池:通过调用`MyThreadPoolExecutor`类的`shutdown()`方法可以优雅地关闭线程池,等待当前正在执行的任务完成后再关闭线程池。 这只是一个简单线程池实现,如果对线程池实现原理和更高级的应用有兴趣,可以进一步了解和探索更多的线程池实现代码库。 ### 回答3: C语言中的线程池可以通过使用Pthreads库来实现。以下是一个简单C语言线程池的代码实现,你可以在GitHub上找到完整的代码。 #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define MAX_THREADS 10 #define MAX_QUEUE 1000 typedef struct { void (*function)(void *); // 线程执行的函数指针 void *argument; // 函数参数 } task_t; task_t task_queue[MAX_QUEUE]; int queue_size = 0; int head = 0; int tail = 0; pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER; pthread_t worker_threads[MAX_THREADS]; int pool_shutdown = 0; // 添加任务到线程池 void pool_add_task(void (*function)(void *), void *argument) { pthread_mutex_lock(&queue_mutex); if (queue_size >= MAX_QUEUE) { fprintf(stderr, "Warning: task queue is full, the task is dropped.\n"); pthread_mutex_unlock(&queue_mutex); return; } task_queue[tail].function = function; task_queue[tail].argument = argument; tail = (tail + 1) % MAX_QUEUE; queue_size++; pthread_cond_signal(&queue_cond); pthread_mutex_unlock(&queue_mutex); } // 线程池的工作线程函数 void *worker(void *arg) { while (1) { pthread_mutex_lock(&queue_mutex); while (queue_size == 0 && !pool_shutdown) { pthread_cond_wait(&queue_cond, &queue_mutex); } if (pool_shutdown) { pthread_mutex_unlock(&queue_mutex); pthread_exit(NULL); } void (*function)(void *) = task_queue[head].function; void *argument = task_queue[head].argument; head = (head + 1) % MAX_QUEUE; queue_size--; pthread_mutex_unlock(&queue_mutex); function(argument); } } // 初始化线程池 void pool_init() { int i; for (i = 0; i < MAX_THREADS; i++) { pthread_create(&worker_threads[i], NULL, worker, NULL); } } // 关闭线程池 void pool_shutdown() { int i; pool_shutdown = 1; pthread_mutex_lock(&queue_mutex); pthread_cond_broadcast(&queue_cond); pthread_mutex_unlock(&queue_mutex); for (i = 0; i < MAX_THREADS; i++) { pthread_join(worker_threads[i], NULL); } } // 测试函数 void print_number(void *arg) { int number = *((int *)arg); printf("Number: %d\n", number); } int main() { pool_init(); int i; for (i = 0; i < 100; i++) { int *number = malloc(sizeof(int)); *number = i; pool_add_task(print_number, (void *)number); } pool_shutdown(); return 0; } 这个线程池实现包括了添加任务到队列,工作线程从请求队列中获取任务并执行,还有线程池的初始化和关闭。你可以在任务函数中处理自己的逻辑,此处的示例是打印数字。注意,这只是一个简单线程池实现,还有许多其他特性可以添加。你可以在GitHub上查找更多更完整的C语言线程池实现

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值