线程池是一种线程使用模式。
线程过多会带来调度开销,进而影响缓存局部和整体性能,而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。
(1)线程池中可用线程的数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
(2)任务来的时候创建线程是要花时间的,如果任务来的时候创建线程耗费时间是花在用户头上。
线程池常见的应用场景如下:
相关解释:
(1)交互接口
(2)代码实现
①threadPool.hpp
#pragma once
#include
#include
#include
#define NUM 5
template
class ThreadPool{
private:
int thread_num;
std::queue task_q;
pthread_mutex_t lock;
pthread_cond_t cond;
public:
ThreadPool(int num = NUM):thread_num(num)
{
pthread_mutex_init(&lock,nullptr);
pthread_cond_init(&cond,nullptr);
}
~ThreadPool()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
public:
void InitTheadPool()
{
pthread_t tid;
for(int i = 0 ; i < thread_num ;++i){
pthread_create(&tid , nullptr , Routine ,this); // !!
}
}
static void* Routine(void* arg)
{
pthread_detach(pthread_self());
ThreadPool* self = (ThreadPool*)arg;
while(true){
self->Lock();
while(self->IsEmpty()){
self->Wait();
}
//走到这里一定有任务
T task;
self->Pop(task);
self->Unlock();
//有些任务处理时间可能较长,解锁之后再处理,否则就成串行了
task.Run();
}
}
public:
void Push(const T& in)
{
Lock();
task_q.push(in);
Unlock();
Wakeup();
}
void Pop(T& out)
{
out = task_q.front();
task_q.pop();
}
void Lock()
{
pthread_mutex_lock(&lock);
}
void Unlock()
{
pthread_mutex_unlock(&lock);
}
void Wait()
{
pthread_cond_wait(&cond,&lock);
}
void Wakeup()
{
pthread_cond_signal(&cond);
}
bool IsEmpty()
{
return task_q.size() == 0 ? true:false ;
}
};
1.线程池中需要用到互斥锁和条件变量
注意:
2.唤醒条件变量下等的线程用signal而不用broadcast
什么是惊群效应:
惊群效应消耗了什么:
3.为什么线程池中的线程执行例程需要设置为静态方法?
②Task.hpp : 无论该任务是什么类型的,在该任务类当中都必须包含一个Run方法,当我们处理该类型的任务时只需调用该Run方法即可。
#pragma once
#include
#include
//#define int (*handler_t)(int,int,char) handler_t//自定义回调方法
class Task{
private:
int x;
int y;
char op;
public:
Task()
{}
Task(int _x,int _y,char _op):x(_x),y(_y),op(_op)
{}
~Task(){}
public:
void Run()
{
int z = 0;
switch(op){
case '+':
z = x + y;
break;
case '-':
z = x - y;
break;
case '*':
z = x * y;
break;
case '/':
if(y == 0) std::cerr << "div zero!"<< std::endl;
if(y != 0) z = x / y;
break;
case '%':
if(y == 0) std::cerr << "mod zero!"<< std::endl;
if(y != 0) z = x % y;
break;
default:
std::cerr << "operator error!" << std::endl;
break;
}
std::cout <<"thread: ["<< pthread_self() << " ]: "
<< x << op << y <<"="<< z <
③main.cc
#include"Task.hpp"
#include"threadPool.hpp"
#include
#include
#include
int main()
{
ThreadPool* tp = new ThreadPool();
tp->InitTheadPool();
srand((unsigned long)time(nullptr));
const char* op = "+-*/%";
while(true){
int x = rand()%100 + 1;
int y = rand()%100 + 1;
Task t(x,y,op[x%5]); //生成任务
tp->Push(t); //塞任务
sleep(1);
}
return 0;
}
④结果: 线程在处理时会呈现出一定的顺序性,因为主线程是每秒Push一个任务,这五个线程只会有一个线程获取到该任务,其他线程都会在等待队列中进行等待,当该线程处理完任务后就会因为任务队列为空而排到等待队列的最后,当主线程再次Push一个任务后会唤醒等待队列首部的一个线程,这个线程处理完任务后又会排到等待队列的最后,因此这线程在处理任务时会呈现出一定的顺序性。
(1)读写锁
(3)其他概念
①写独占,读共享,写锁优先级高
②“321”中: 读者和读者之间对数据是共享的,本质是因为消费者会取走数据,读者不会,读者是拷贝数据。
③读写优先
④读者写者的伪代码
(1)自旋锁: spin 因为占有临界资源的线程,在临界区内待的时间特别短,无需挂起,让当前线程处于自旋状态,不断去检测锁的状态(非阻塞式申请锁,申请失败继续申请),而其中,自旋锁为我们提供上述功能!
(2)—个执行流在临界资源待的时间长短问题。
(3)小结