线程池
思想
线程池的核心思想相对于one TcpConnection per thread来说。有如下优点。
- 频繁创建和消耗线程开销很大,如果业务很短,开销比创建线程还小,那么就得不偿失。线程池采用固定大小的线程数,减少了这部分消耗。
- 大并发会导致起非常多的线程,从而吃光内存。
- 多线程并非很多线程就很好。实际上和CPU的核心数有关。
API
class ThreadPool
{
public:
ThreadPool(int threads, int MaxTasks);
bool addTask(const Task&);
void destroy();
~ThreadPool();
};
个人认为有如上的API使用足以。
任务类
线程池的一个关键是抽象人物类
我们有很多种做法
- 类模板
- 利用bind/functiona采用基于对象的方式
- 利用多态
个人比较偏好第二种做法,后面会简单介绍几种做法。先看下如何传递线程函数入口
如何传递线程函数的入口
这里简单说明下
由于pthread_create()
传递给它的只能是静态函数,我们一般会设置一个
static void* threadFunc(void* arg)
这样的静态成员函数。
因此这个静态成员函数被我们作为线程函数的入口。而且它一般都是写死的。
void*
ThreadPool::threadFunc(void *arg)
{
ThreadPool * pool = static_cast<ThreadPool*> arg;
pool->run();
return pool;
}
这就需要我们在创建线程的时候交给线程函数this指针。即
pthread_create(&threadId, NULL, threadFunc, this);
第一种做法:采取模板的做法
template <typename T>
class ThreadPool
{
public:
...
private:
run();
private:
queue<Task*> tasks_;
}
第二种做法:采取function的做法
class ThreadPool
{
typedef function<void()> Task;
private:
queue<Task> tasks_;
}
第三种做法:采取继承的方式
需要新增加一个Task虚基类
class Task
{
public:
virtual process() = 0;
virtual ~Task() = default;
}
说白了,这里的核心人物就是为了抽象任务类。从而引发了三种不同的方式来抽象任务类。
任务队列——BlockingQueue
接下来的关键实际上就是实现一个阻塞队列。
阻塞队列可以有三个API,需要用到条件变量和锁的结合
扫描二维码关注公众号,回复:
6656961 查看本文章
template <typename T>
class BlockingQueue
{
public:
...
bool put();
T get();
size_t size()const;
private:
MutexLock mutex_;
Condition cond_;
}
BlockingQueue是一个线程安全的阻塞队列。当队列为空时,如果我们要拿取任务,该线程会阻塞在这里,直到有其它线程向里面添加任务为止。