Linux:带你理解线程池


概念

线程的池子,有很多线程,但是数量不会超过池子的限制。(需要用到多执行流并行进行任务处理的时候,就从池子中取出)

一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络 sockets等的数量。

为什么用线程池

有大量的数据处理请求,需要多执行流并发/并行处理

若是一个数据请求的到来伴随一个线程的创建去处理,则会产生一些风险以及一些不必要的消耗:

  1. 线程若不限制数量的创建,在峰值压力下,线程创建过多,资源耗尽,有程序崩溃的风险
  2. 处理一个任务的时间:创建线程时间t1+任务处理时间t2+线程销毁时间t3 = T。若t2/T比例占据不够高,则表示大量的资源用于线程的创建与销毁成本上,因此线程池使用已经创建好的线程进行循环任务处理,就避免了大量线程的频繁创建于销毁的时间成本.

应用场景

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.

实现一个线程池

大量线程(每个线程中都是进行循环的任务处理)+ 任务缓冲队列

  • 线程的入口函数,都是在创建线程的时候,就固定传入的,导致线程池中的线程进行任务处理的方式过於单一
  • 因为线程的入口函数都是一样的,处理流程也就都是一样的,只能处理单一方式的请求。 - 灵活性太差

若任务队列中的任务,不仅仅是单纯的数据,而是包含任务处理方式在内的数据,这时候,线程池中的线程只需要使用传入的方式,处理传入的数据即可,不需要关心是什么数据,如何处理。 - 提高线程池的灵活性

typedef void (*_handler)(int data);
class MyTask{
private:
    int _data;    // 要处理的数据
    handle_t handler;    // 处理数据的方法
public:
    SetTask(int data, handler_t handler);    // 让用户自己传入要处理的数据和方法,组织出一个任务节点
    Run(){ 
        return _handle(_data);
    }
};

每个线程的入口函数中,只需要不断的获取任务节点,调用任务节点中的Run接口就可以实现任务的处理。

#include <iostream>
#include <cstdio>
#include <queue>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

#define MAX_THREAD 5

typedef void (*handler_t)(int);

class ThreadTask{
public:
	ThreadTask():_data(-1), _handler(NULL)
	{
	}
	void SetTask(int data, handler_t handler){
		_data = data;
		_handler = handler;
	}
	void Run(){	// 外部只需要调用Run,不需要关心任务如何处理
		return _handler(_data);
	}
private:
	int _data;
	handler_t _handler;
};

class ThreadPool{
public:	
	ThreadPool(int max_thr = MAX_THREAD):
		_thr_max(max_thr)
	{
		pthread_mutex_init(&_mutex, NULL);
		pthread_cond_init(&_cond, NULL);
		for(int i = 0; i < _thr_max; i++){
			pthread_t tid;
			int ret = pthread_create(&tid, NULL, thr_start, this);
			if(ret != 0){
				printf("thread create error\n");
				exit(-1);
			}
		}
	}
	~ThreadPool(){
		pthread_mutex_destroy(&_mutex);
		pthread_cond_destroy(&_cond);
	}
	bool TaskPush(ThreadTask &task){
		pthread_mutex_lock(&_mutex);
		_queue.push(task);
		pthread_mutex_unlock(&_mutex);
		pthread_cond_broadcast(&_cond);	// 入队后唤醒所有线程,谁抢到谁处理
		return true;
	}

	// 类的成员函数,有一个隐藏的默认参数(this指针)
	// 线程的入口函数只有一个参数,所以设置为static
	static void *thr_start(void * arg){
		ThreadPool *p = (ThreadPool *) arg;
		// 不断的从任务队列中取出任务,执行任务的Run接口就可以
		while(1){
			pthread_mutex_lock(&p->_mutex);
			while(p->_queue.empty()){
				pthread_cond_wait(&p->_cond, &p->_mutex);
			}
			ThreadTask task;
			task = p->_queue.front();
			p->_queue.pop();
			pthread_mutex_unlock(&p->_mutex);
			task.Run();	// 任务的处理要放在解锁之外,因为当前的锁保护的是队列的操作
		}
		return NULL;
	}
private:
	int _thr_max;	// 线程池中线程的最大数量 - 根据这个初始化创建指定数量的线程
	std::queue<ThreadTask> _queue;
	pthread_mutex_t _mutex;	// 保护队列操作的互斥锁
	pthread_cond_t _cond;	// 实现从队列中获取节点的同步条件变量
};

void test_func(int data){
	int sec = (data % 3) + 1;
	printf("tid:%p -- get data:%d , sleep:%d\n", pthread_self(), data, sec);
	sleep(sec);
}

void tmp_func(int data){
	printf("tif:%p -- tmp_func\n", pthread_self());
	sleep(1);
}

int main(){
	ThreadPool pool;
	for(int i = 0; i < 10; i++){
		ThreadTask task;
		if(i % 2 == 0)
			task.SetTask(i, test_func);
		else
			task.SetTask(i, tmp_func);

		pool.TaskPush(task);
	}
	sleep(1000);
	return 0;
}

一次运行结果:

在这里插入图片描述


如果本篇博文有帮助到您,留个赞激励一下博主呐~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章