线程池是一种线程使用模式。
线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线
程,等待着监督管理者分配可并发执行的任务。
可用线程的数量取决于可用的并发处理器、处理器内核、内存、网络、sockets等。
(1).需要大量的线程来完成任务,且完成任务的时间比较短。
比如:WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大 。
(2). 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
(3).接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。
突发性大量客户请求,在没有线程池情况下,将产生大量线程,短时间内产生大量线程可能使内存到达极限。
线程池对外暴露一个接口push接口,用于任务的加入。
图示:
threadpool.hpp
实现线程模板:
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int Thread_num=10;
template <class T>
class threadpool{
private:
threadpool(const int num=Thread_num):threadnum(num)
{
assert(threadnum>0);
isrunning=false;
pthread_cond_init(&cond_,nullptr);
pthread_mutex_init(&mutex_,nullptr);
}
threadpool(const threadpool<T> &)=delete; //拷贝构造
threadpool<T>& operator=(const threadpool<T>&)=delete;
public:
~threadpool(){
pthread_mutex_destroy(&mutex_);
pthread_cond_destroy(&cond_);
}
static threadpool<T>*getInstance(){
static pthread_mutex_t mutex_static;
if(instance==nullptr){
//判断是否需要创建线程池
pthread_mutex_lock(&mutex_static);
if(instance==nullptr){
instance=new threadpool<T>();
}
pthread_mutex_unlock(&mutex_static);
}
pthread_mutex_destroy(&mutex_static);
return instance;
}
//类内函数默认第一个参数是this指针,而由于线程的执行函数只有一个参数,所以设置为静态函数,否则第一个参数被this占用
static void* threadroutine(void* arg){
pthread_detach(pthread_self()); //分离线程
threadpool<T>* pool=static_cast<threadpool<T>* >(arg);
prctl(PR_SET_NAME, "follower");
//线程不断的获取任务,并执行
while (true)
{
pool->lockQueue();
//判断是否为空
//为空则等待唤醒
//不为空,取任务执行
while (!pool->haveTask())
{
pool->waitForTask();
}
T t=pool->pop();
int em1,em2;
char op;
t.get(&em1,&em2,&op);
cout << "consumer[" << pthread_self() << "] " << (unsigned long)time(nullptr)
<< " 消费了一个任务: " << em1 << op << em2 << "=" << t() << endl;
pool->unlockQueue();
}
return nullptr;
}
void start(){
assert(!isrunning);
for(int i=0;i<threadnum;i++){
pthread_t tid;
pthread_create(&tid,nullptr,threadroutine,(void*)instance);
}
}
void push(const T& t)
{
lockQueue();
workqueue_.push(t);
choiceThreadForHandler();
unlockQueue();
}
private:
void lockQueue() { pthread_mutex_lock(&mutex_); }
void unlockQueue() { pthread_mutex_unlock(&mutex_); }
bool haveTask() { return !workqueue_.empty(); }
void waitForTask() { pthread_cond_wait(&cond_, &mutex_); }
void choiceThreadForHandler() { pthread_cond_signal(&cond_); }
T pop()
{
T temp =workqueue_.front();
workqueue_.pop();
return temp;
}
queue<T> workqueue_; //工作队列
int threadnum; //线程数量
pthread_mutex_t mutex_;
pthread_cond_t cond_;
bool isrunning; //判断线程池是否允许
static threadpool<T>* instance;
};
template<class T>
threadpool<T>* threadpool<T>::instance=nullptr;
注意点:
pthread_cond_broadcast
函数的作用是唤醒条件变量下的所有线程,而外部可能只Push了一个任务,我们却把全部在等待的线程都唤醒了,此时这些线程就都会去任务队列获取任务,但最终只有一个线程能得到任务。一瞬间唤醒大量的线程可能会导致系统震荡。这个现象也叫做惊群效应为什么线程函数要设置为静态类型?
void*
的参数。测试文件
#include "threadpool.hpp"
#include "task.hpp"
#include
#include
const std::string ops = "+-*/%";
int main()
{
prctl(PR_SET_NAME, "main");
//使用智能指针
unique_ptr<threadpool<Task>>pool(threadpool<Task>::getInstance());
pool->start();
//生产任务
srand((unsigned long)time(nullptr) ^ getpid() ^ pthread_self());
while (true)
{
int em1=rand()%100,em2=rand()%30;
char op=ops[rand()%4];
Task t(em1,em2,op);
pool->push(t);
cout << "producter[" << pthread_self() << "] " << (unsigned long)time(nullptr)
<< " 生产了一个任务: " << em1 << op << em2 << "=?" << endl;
sleep(1);
}
return 0;
}
使用下面的指令对轻量级线程进行监控:
while :; ps -aL|grep -1&&ps -aL|grep threadpool_test|grep -v grep;echo "#######";sleep 1;done
执行结果:
STL中的容器不是线程安全的
智能指针是否是线程安全的?
自旋锁和挂起等待锁
挂起等待锁适合在临界区长时间允许占有锁的情况。而挂起等待锁适合在临界区运行时间短,等待锁时间短的情况。
自旋锁的接口
pthread_spin_init();
与互斥锁的接口一样,只需要将mutex修改为spin即可
在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读
的机会反而高的多。
通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。而读写锁就可以解决读多写少的情况。
接口
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);
/*
pref 共有 3 种选择
PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和
PTHREAD_RWLOCK_PREFER_READER_NP 一致
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁
*/
初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t
*restrict attr);
销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
**加锁和解锁 **
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);