一、线程池
1.概念
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着 监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利 用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
2.模拟实现
ThreadPool.hpp
#pragma once
#include<iostream>
#include<queue>
#include<pthread.h>
#define NUM 5
template <typename T>
class ThreadPool
{
private:
int num;
std::queue<T> task_queue; //任务队列,临界资源
pthread_mutex_t lock; //对临界资源进行保护
pthread_cond_t cond; //有休眠就有唤醒
public:
ThreadPool(int _num = NUM):thread_num(_num)
{
pthread_mutex_init(&lock); //对锁初始化
pthread_cond_init(&cond,nullptr);
}
void LockQueue()
{
pthread_mutex_lock(&lock); //加锁
}
void UnlockQueue()
{
pthread_mutex_unlock(&lock); //解锁
}
bool IsQueueEmpty()
{
return task_queue.size() == 0 ? true : false;
}
void Wait()
{
pthread_cond_wait(&cond,&lock);
}
void WakeUp()
{
pthread_cond_signal(&cond);
}
static void *Routine(void *arg) //static:这个方法属于类,不属于对象,只会传入所想要传入的参数,不会有this指针
{
pthread_detach(pthread_self());
ThreadPool *self = (ThreadPool*)arg;
while(true)
{
self->LockQueue(); //为了访问非静态成员函数
while(self()->IsQueueEmpty())
{
//wait
self->Wait();
}
//任务队列一定有队列
T t;
self->Pop(); //拿任务
self->UnlockQueue();
//处理任务
t.Run();
}
}
void InitThreadPool()
{
pthread_t tid;
for(int i = 0; i < thread_num; i++)
{
pthread_create(&tid,nullptr,Routine,this);
pthread_detach(tid);
}
}
void Push(const T& in)
{
LockQueue();
task_queue.push(in);
UnlockQueue();
WakeUp();
}
void Pop(T &out)
{
out = task_queue.front();
task_queue.pop();
}
~ThreadPool()
{
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&lock);
}
}
task.hpp
#pragma once
#include<iostream>
#include<pthread.h>
//typedef int (*handler_t)(int,int,char); //回调函数
class Task
{
private:
int x;
int y;
char op;
//handler_t handler;
public:
Task()
{}
Task(int _x,int _y,chat _op):x(_x),y(_y),op(_op)
{
}
void Run()
{
//任务处理
//int ret = handler(x,y,op);
int z = 0;
swotch(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:
perror("operator error!\n");
break;
}
std::cout << "thread:[" << pthread_self() << "]:" << x << op << y << "="<< z << std::endl;
}
~Task()
{}
}
main.cc
#include"Task.hpp"
#include"ThreadPool.hpp"
#include<cstdlib>
#include<ctime>
#include<unistd.h>
int main()
{
ThreadPool<Task> *tp = new ThreadPool<Task>();
tp->InitThreadPool();
srand((unsigned long)time(nullptr));
const char *op = "+-*/%";
while(true)
{
int x = rand()%100+1; //[0.100]
int y = rand()%100+1;
Task t(x,y,op[x%5]); //x%5对应0-4
tp->Push(t); //任务塞队列
sleep(1);
}
return 0;
}
二、线程安全的单例模式
1.设计模式
对于常见的场景,给定对应的解决方案
2.单例模式
(1)特点
某些类,只应该具有一个对象(实例),就称之为单例
(2)实现方式
饿汉实现方式
template <typename T>
class Singleton
{
static T data; //对象已经存在
public:
static T* GetInstance()
{
return &data;
}
};
懒汉实现方式核心思想:“延时加载”,从而优化服务器的启动速度
template <typename T>
class Singleton
{
static T* inst;
public:
static T* GetInstance()
{
if(inst == NULL)
{
inst = new T();
}
return inst;
}
};
存在问题:线程不安全,第一次调用Getinstance的时候,如果两个线程同时调用,可能会创建两份T对象的实例,但是后续再次调用,就没有问题了
改进:线程安全版本
template <typename>
class Singleton
{
volatile static T* inst; //设置volatile,否则可能会被编译器优化
static std::mutex lock;
public:
static T* GetInstance()
{
if(inst == NULL) //双重判定空指针,降低锁冲突的概率,提高性能
lock.lock(); //使用互斥锁,保证多线程情况下也只能调用一次
{
if(inst == NULL)
{
inst = new T();
}
lock.unlock();
}
return inst;
}
};
注意:
(1)加锁解锁的位置
(2)双重if判定,避免不必要的锁竞争
(3)volatile关键字防止过度优化
三、读写锁
1.主要处理多读少写的情况
三种关系:读者和读者(没有冲突),写者和写者(互斥关系),读者和写者(互斥,同步)
读和写
一个交易场所
2.应用场景
(1)数据写下之后,剩下操作就是读取
(2)写的操作少,读操作多
3.读者,按照读的方式加锁;写者,按照写的方式加锁,读写接口
(1)设置读写优先
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 写者优先,但写者不能递归加锁 */
(2)初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
(3)销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
(4)加锁和解锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
多线程情况下,读写同时到来,如果还有人在读,接下来的读的角色,不要进入临界区进行读,等写着进入,并写完,然后你再读(写者优先);反之就是读者优先