🌟🌟hello,各位读者大大们你们好呀🌟🌟
🚀🚀系列专栏:【Linux的学习】
📝📝本篇内容:信号量;基于环形队列的生产消费模型;线程池;STL和线程安全;各种锁;读者写者问题
⬆⬆⬆⬆上一篇:Linux多线程(中)
💖💖作者简介:轩情吖,请多多指教(>> •̀֊•́ ) ̖́-
1.信号量
1.1基本概念
信号量,又名信号灯,本质上是一个计数器
信号量需要进行PV操作,P(–),V(++),两者都是原子性的
当信号量从1到0再到1时,只有两种状态,称为二元信号量,它等同于互斥锁
信号量是用来描述临界资源中资源数目的
还有一种叫做多元信号量
每一个线程,在访问对应的资源的时候,先申请信号量,申请成功则表示该线程允许使用该资源,申请不成功,表示目前无法使用该资源
信号量的工作机制是一种资源的预订机制
信号量已经是资源的计数器了,申请信号量成功本身就表明资源可用。申请信号量失败表明资源不可用,本质上把判断转换成为信号量的申请行为。
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源的目的,但POSIX可以用于线程间同步
1.2信号量函数
参数:
pshared:0表示线程间共享,非0表示进程间共享
value:信号量初始值
sem_wait()函数就是用来实现P自减的操作,等待信号量,会将信号量的值减1
sem_post函数就是用来实现V自增操作的,表示发布信号量,表示资源使用完毕,可以归还资源了,将信号量值加1
具体使用看下面环形队列的代码
2.基于环形队列的生产消费模型
环形队列采用数组模拟,用模运算来模拟环形特性
环形结构起始状态和结束状态都是一模一样的,不好判断是空是满,所以可以通过计数器或者标记位来判断满或空,另外也可以预留一个空的位置,作为满状态
现在可以通过信号量这个计数器
消费者向tail中push数据;消费者向head中pop数据
2.1构建CP问题
①生产者关心的是空间,消费者关心的是数据
②只要信号不为0,表示资源可用,表示线程可访问
③环形队列只要我们访问不同的区域,生产和消费可以同时进行
④消费者生产者访问同一个区域:消费者和生产者刚开始的时候,没有数据,为空时,指向同一个位置,存在竞争关系,生产者先运行;数据满时,消费者先运行
⑤只要为空和为满时,cp才会指向同一个位置,其他情况,cp可以并发运行
⑥我们要保证这个规则,同时也要保证空或者满时候的策略问题:不能让生产者套圈消费者;不能让消费者超过生产者
2.2示例代码
//RingQueue.hpp
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <semaphore.h>
using namespace std;
#define SIZE 5
template<class T>
class RingQueue
{
public:
RingQueue()
:_ring(SIZE)
{
sem_init(&_customer,0,0);
sem_init(&_productor,0,_capacity);
_pos_customer=_pos_productor=0;
pthread_mutex_init(&_mutex_customer,nullptr);
pthread_mutex_init(&_mutex_productor,nullptr);
}
~RingQueue()
{
sem_destroy(&_customer);
sem_destroy(&_productor);
pthread_mutex_destroy(&_mutex_customer);
pthread_mutex_destroy(&_mutex_productor);
}
void push(const T& data)
{
sem_wait(&_productor);//等待信号量,如果有就申请成功
pthread_mutex_lock(&_mutex_productor);
_pos_productor%=_capacity;//保证环形队列
_ring[_pos_productor++]=data;
pthread_mutex_unlock(&_mutex_productor);
sem_post(&_customer);//此时有数据了,唤醒消费者
}
void pop(T* data)
{
sem_wait(&_customer);//如果申请失败,就阻塞等待
pthread_mutex_lock(&_mutex_customer);
_pos_customer%=_capacity;
*data=_ring[_pos_customer++];
pthread_mutex_unlock(&_mutex_customer);
sem_post(&_productor);//原子的
}
private:
vector<T> _ring;//环形队列
sem_t _customer;//消费者的信号量,只有消费者关心的数据
sem_t _productor;//生产者的信号量,只有生产者关心的空间
int _pos_customer;//消费位置;保证环形队列的特性而不越界
int _pos_productor;//生产位置;保证环形队列的特性而不越界
pthread_mutex_t _mutex_productor;//来限制生产者线程的锁
pthread_mutex_t _mutex_customer;//来限制消费者线程的锁
int _capacity=SIZE;//环形队列的大小
};
//main.cc
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>
#include "Task.hpp"
#include "RingQueue.hpp"
using namespace std;
void *ProductorRun(void *arg) // 生产者线程要使用的函数
{
RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(arg);
while (1)
{
// 创建Task任务
char arr[] = "+-*/%";
int x = rand() % 10;
int y = rand() % 20;
char c = arr[rand() % 5];
Task t(x, y, c);
rq->push(t);
cout << t.to_productor() << endl;
}
return nullptr;
}
void *CustomerRun(void *arg) // 消费者线程要使用的函数
{
RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(arg);
while (1)
{
sleep(3);
Task t;
rq->pop(&t);
t();
cout << t.to_customer() << endl;
}
return nullptr;
}
int main()
{
srand((uint64_t)time(0));
RingQueue<Task>* rq=new RingQueue<Task>;
pthread_t customer[3];
pthread_t productor[3];
pthread_create(&customer[0],nullptr,CustomerRun,rq);
pthread_create(&customer[1],nullptr,CustomerRun,rq);
pthread_create(&customer[2],nullptr,CustomerRun,rq);
pthread_create(&productor[0],nullptr,ProductorRun,rq);
pthread_create(&productor[1],nullptr,ProductorRun,rq);
pthread_create(&productor[2],nullptr,ProductorRun,rq);
//join
pthread_join(customer[0],nullptr);
pthread_join(customer[1],nullptr);
pthread_join(customer[2],nullptr);
pthread_join(productor[0],nullptr);
pthread_join(productor[1],nullptr);
pthread_join(productor[2],nullptr);
return 0;
}
//Task.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include<string>
using namespace std;
//环形队列中的内容,生产者生产,消费者消费
class Task
{
public:
Task()
{}
Task(int x,int y,char op)
:_x(x),_y(y),_op(op)
{}
void operator()()
{
switch(_op)
{
case '+':
_result=_x+_y;
break;
case '-':
_result=_x-_y;
break;
case '/':
if(_y==0)
{
_code=-1;
break;
}
_result=_x/_y;
break;
case '*':
_result=_x*_y;
break;
case '%':
if(_y==0)
{
_code=-2;
break;
}
_result=_x%_y;
break;
default:
_code=-3;
break;
}
}
string to_productor()//打印productor生产的内容是什么
{
return to_string(_x)+_op+to_string(_y)+"=?";
}
string to_customer()//打印customer消费后的结果为什么
{
return to_string(_x)+_op+to_string(_y)+"="+to_string(_result)+";code="+to_string(_code);
}
private:
int _x;//操作数
int _y;//操作数
char _op;//计算方法
int _result=0;//计算结果
int _code=0;//计算后的返回码
};
//_code:
//-1 -> /出错
//-2 -> %出错
//-3 -> _op出错
# makefile
main:main.cc
g++ -o main main.cc -std=c++11 -lpthread
.PHONY:clean
clean:
rm -rf main
根据对应的临界资源是否被整体使用来确定用锁还是sem
3.线程池
一种线程的使用模式,线程过多会带来调度开销进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监管者分配可并发执行的任务,这避免了在处理短时间任务时创建和销毁线程的代价,线程池不仅能够保证内核的充分利用,还能防止过分调度
我这边一共演示了四个版本:
V1:此版本的线程池是使用的是系统的pthread
//main.cc
#include "ThreadPoolV1.hpp"
#include "Task.hpp"
#include <cstring>
#include <memory>
int main()
{
srand((uint64_t)time(0));
unique_ptr<ThreadPool<Task>> pool(new ThreadPool<Task>);
pool->Start();
while(1)
{
int x=rand()%10;
int y=rand()%20;
char arr[]="+-*/%";
char op=arr[rand()%strlen(arr)];
pool->push(Task(x,y,op));
// sleep(1);
}
return 0;
}
//ThreadPoolV1.hpp
//此版本的线程池是使用的是系统的pthread
#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>
#include <vector>
#include <queue>
#include "Task.hpp"
using namespace std;
#define SIZE 5
template<class T>
class ThreadPool
{
public:
ThreadPool(int size=SIZE)
:_pool(size),//提前把空间开好,后面就可以直接访问了而不会越界访问
_capacity(size)
{
pthread_mutex_init(&_mutex,nullptr);
pthread_cond_init(&_cond,nullptr);
}
~ThreadPool()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
void push(const T& task)
{
pthread_mutex_lock(&_mutex);
_task.push(task);
pthread_cond_signal(&_cond);//唤醒线程来执行task
pthread_mutex_unlock(&_mutex);
}
static void* Routine(void* arg)
{
ThreadPool* tp=static_cast<ThreadPool*>(arg);
while(1)
{
//所有的线程都在这里面运行,省去了创建和销毁的开销
pthread_mutex_lock(&tp->_mutex);
while(tp->_task.empty())//使用while而不是if可以更安全,即使意外唤醒了
{
pthread_cond_wait(&tp->_cond,&tp->_mutex);
}
T t=tp->_task.front();//获取任务
tp->_task.pop();
pthread_mutex_unlock(&tp->_mutex);
t();//完成任务
cout<<t.to_customer()<<endl;
}
}
void Start()
{
for(int i=0;i<_capacity;i++)
{
pthread_create(&_pool[i],nullptr,Routine,this);
}
}
private:
vector<pthread_t> _pool;//线程池
queue<T> _task;//任务
int _capacity;//线程池的大小
pthread_mutex_t _mutex;//保护task的资源
pthread_cond_t _cond;//在没有task时进行一个等待
};
//Task.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include<string>
using namespace std;
//阻塞队列中的内容,生产者生产,消费者消费
class Task
{
public:
Task()
{}
Task(int x,int y,char op)
:_x(x),_y(y),_op(op)
{}
void operator()()
{
switch(_op)
{
case '+':
_result=_x+_y;
break;
case '-':
_result=_x-_y;
break;
case '/':
if(_y==0)
{
_code=-1;
break;
}
_result=_x/_y;
break;
case '*':
_result=_x*_y;
break;
case '%':
if(_y==0)
{
_code=-2;
break;
}
_result=_x%_y;
break;
default:
_code=-3;
break;
}
}
string to_productor()//打印productor生产的内容是什么
{
return to_string(_x)+_op+to_string(_y)+"=?";
}
string to_customer()//打印customer消费后的结果为什么
{
return to_string(_x)+_op+to_string(_y)+"="+to_string(_result)+";code="+to_string(_code);
}
private:
int _x;//操作数
int _y;//操作数
char _op;//计算方法
int _result=0;//计算结果
int _code=0;//计算后的返回码
};
//_code:
//-1 -> /出错
//-2 -> %出错
//-3 -> _op出错
# makefile
main:main.cc
g++ -o main main.cc -std=c++11 -lpthread
.PHONY:clean
clean:
rm -rf main
V2:此版本的线程池是使用的是自己写的Thread对象,对pthread_t进行了封装
//main.cc
// #include "ThreadPoolV1.hpp"
#include "ThreadPoolV2.hpp"
#include "Task.hpp"
#include <cstring>
#include <memory>
int main()
{
srand((uint64_t)time(0));
unique_ptr<ThreadPool<Task>> pool(new ThreadPool<Task>);
pool->Init();
pool->Start();
pool->Check();
while(1)
{
int x=rand()%10;
int y=rand()%20;
char arr[]="+-*/%";
char op=arr[rand()%strlen(arr)];
pool->push(Task(x,y,op));
sleep(1);
}
return 0;
}
// ThreadPoolV2.hpp
// 此版本的线程池是使用的是自己写的Thread对象
#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>
#include <vector>
#include <queue>
#include "Task.hpp"
#include "Thread.hpp"
using namespace std;
#define SIZE 5
template <class T>
class ThreadPool
{
public:
ThreadPool(int size = SIZE)
: //这里不用把线程池空间先开好,不然会出现没有默认构造函数问题
_capacity(size)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_cond, nullptr);
}
~ThreadPool()
{
for(int i=0;i<_capacity;i++)
{
_pool[i].Join();
}
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
void Init() // 进行一个初始化,把我们的一个Thread对象放进线程池
{
for (int i = 0; i < _capacity; i++)
{
_pool.push_back(Thread(i, Routine, this));
}
}
void Start() // 我们的线程真正开始执行起来
{
for (int i = 0; i < _capacity; i++)
{
_pool[i].Run(); // 调用Thread对象的Run函数
}
}
void push(const T &task)
{
pthread_mutex_lock(&_mutex);
_task.push(task);
pthread_cond_signal(&_cond); // 唤醒线程来执行task
pthread_mutex_unlock(&_mutex);
}
static void *Routine(void *arg)
{
ThreadPool *tp = static_cast<ThreadPool *>(arg);
while (1)
{
// 所有的线程都在这里面运行,省去了创建和销毁的开销
pthread_mutex_lock(&tp->_mutex);
while (tp->_task.empty()) // 使用while而不是if可以更安全,即使意外唤醒了
{
pthread_cond_wait(&tp->_cond, &tp->_mutex);
}
T t = tp->_task.front(); // 获取任务
tp->_task.pop();
pthread_mutex_unlock(&tp->_mutex);
t(); // 完成任务
cout << t.to_customer() << endl;
}
}
void Check()
{
for (int i = 0; i < _capacity; i++)
{
cout << _pool[i].ThreadName() << endl;
}
}
private:
vector<Thread> _pool; // 线程池
queue<T> _task; // 任务
int _capacity; // 线程池的大小
pthread_mutex_t _mutex; // 保护task的资源
pthread_cond_t _cond; // 在没有task时进行一个等待
};
//Task.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include<string>
using namespace std;
class Task
{
public:
Task()
{}
Task(int x,int y,char op)
:_x(x),_y(y),_op(op)
{}
void operator()()
{
switch(_op)
{
case '+':
_result=_x+_y;
break;
case '-':
_result=_x-_y;
break;
case '/':
if(_y==0)
{
_code=-1;
break;
}
_result=_x/_y;
break;
case '*':
_result=_x*_y;
break;
case '%':
if(_y==0)
{
_code=-2;
break;
}
_result=_x%_y;
break;
default:
_code=-3;
break;
}
}
string to_productor()//打印productor生产的内容是什么
{
return to_string(_x)+_op+to_string(_y)+"=?";
}
string to_customer()//打印customer消费后的结果为什么
{
return to_string(_x)+_op+to_string(_y)+"="+to_string(_result)+";code="+to_string(_code);
}
private:
int _x;//操作数
int _y;//操作数
char _op;//计算方法
int _result=0;//计算结果
int _code=0;//计算后的返回码
};
//_code:
//-1 -> /出错
//-2 -> %出错
//-3 -> _op出错
//Thread.hpp
//自己写的Thread对象
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <vector>
#include <unistd.h>
#include <functional>
using namespace std;
using func_t=function<void*(void*)>;
enum Status
{
NEW,
RUNNING,
EXIT,
};
class Thread
{
public:
Thread(int num,func_t func,void* arg)
:_func(func),
_arg(arg)
{
char buff[1024];
snprintf(buff,sizeof(buff),"thread-%d",num);
_name+=buff;
_status=NEW;
}
void Run()
{
if(0!=pthread_create(&_tid,nullptr,RunHelper,this))
//这边又封装了一层,来让我们线程控制的函数能够让我们进行控制,为后续的线程池做铺垫
{
exit(-1);
}
_status=RUNNING;
}
static void* RunHelper(void* arg)
{
Thread* td=static_cast<Thread*>(arg);
(*td)();//这边也封装了一层
}
void* operator()()
{
return _func(_arg);
}
void Join(void)
{
int n=pthread_join(_tid,nullptr);
if(n!=0)
{
exit(-2);
}
_status=EXIT;
}
string ThreadName()
{
return _name;
}
pthread_t ThreadId()
{
if(_status==RUNNING)
{
return _tid;
}
else
{
return 0;
}
}
private:
string _name;//线程名
pthread_t _tid;//线程id
Status _status;//线程当前的一个状态
func_t _func;//线程执行的函数
void* _arg;//传给线程执行的函数参数
};
//退出码:
//-1-> pthread_create error
//-2-> pthread_join error
# makefile
main:main.cc
g++ -o main main.cc -std=c++11 -lpthread
.PHONY:clean
clean:
rm -rf main
V3:此版本的线程池是使用的是自己写的Thread对象,同时增加了自己写的锁的封装,来实现RAII
//main.cc
// #include "ThreadPoolV1.hpp"
//#include "ThreadPoolV2.hpp"
#include "ThreadPoolV3.hpp"
#include "Task.hpp"
#include <cstring>
#include <memory>
int main()
{
srand((uint64_t)time(0));
unique_ptr<ThreadPool<Task>> pool(new ThreadPool<Task>);
pool->Init();
pool->Start();
pool->Check();
while(1)
{
int x=rand()%10;
int y=rand()%20;
char arr[]="+-*/%";
char op=arr[rand()%strlen(arr)];
pool->push(Task(x,y,op));
sleep(1);
}
return 0;
}
// ThreadPoolV3.hpp
// 此版本的线程池是使用的是自己写的Thread对象,同时增加了自己写的锁的封装,来实现RAII
#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>
#include <vector>
#include <queue>
#include "Task.hpp"
#include "Thread.hpp"
#include "Lock.hpp"
using namespace std;
#define SIZE 5
template <class T>
class ThreadPool
{
public:
ThreadPool(int size = SIZE)
: // 这里不用把线程池空间先开好,不然会出现没有默认构造函数问题
_capacity(size)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_cond, nullptr);
}
~ThreadPool()
{
for (int i = 0; i < _capacity; i++)
{
_pool[i].Join();
}
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
void Init() // 进行一个初始化,把我们的一个Thread对象放进线程池
{
for (int i = 0; i < _capacity; i++)
{
_pool.push_back(Thread(i, Routine, this));
}
}
void Start() // 我们的线程真正开始执行起来
{
for (int i = 0; i < _capacity; i++)
{
_pool[i].Run(); // 调用Thread对象的Run函数
}
}
void push(const T &task)
{
Lock_Guard m(&_mutex);//使用RAII
// pthread_mutex_lock(&_mutex);
_task.push(task);
pthread_cond_signal(&_cond); // 唤醒线程来执行task
// pthread_mutex_unlock(&_mutex);
}
static void *Routine(void *arg)
{
ThreadPool *tp = static_cast<ThreadPool *>(arg);
while (1)
{
T t;
// 所有的线程都在这里面运行,省去了创建和销毁的开销
{
Lock_Guard m(&tp->_mutex);//通过加上括号来实现RAII
// pthread_mutex_lock(&tp->_mutex);
while (tp->_task.empty()) // 使用while而不是if可以更安全,即使意外唤醒了
{
pthread_cond_wait(&tp->_cond, &tp->_mutex);
}
t = tp->_task.front(); // 获取任务
tp->_task.pop();
}
// pthread_mutex_unlock(&tp->_mutex);
t(); // 完成任务
cout << t.to_customer() << endl;
}
}
void Check()
{
for (int i = 0; i < _capacity; i++)
{
cout << _pool[i].ThreadName() << endl;
}
}
private:
vector<Thread> _pool; // 线程池
queue<T> _task; // 任务
int _capacity; // 线程池的大小
pthread_mutex_t _mutex; // 保护task的资源
pthread_cond_t _cond; // 在没有task时进行一个等待
};
//Lock.hpp
//对锁进行一个封装,RAII
#pragma once
#include <pthread.h>
#include <iostream>
using namespace std;
class Lock_Guard
{
public:
Lock_Guard(pthread_mutex_t* mutex)
:_mutex(mutex)
{
pthread_mutex_lock(_mutex);
}
//构造函数的时候加锁,析构函数的时候解锁
~Lock_Guard()
{
pthread_mutex_unlock(_mutex);
}
private:
pthread_mutex_t* _mutex;
};
其余的跟上一个版本一样
V4:此版本的线程池是使用的是自己写的Thread对象,同时增加了自己写的锁的封装,来实现RAII;同时增加单例模式;也是最后的完整代码
//main.cc
// #include "ThreadPoolV1.hpp"
//#include "ThreadPoolV2.hpp"
//#include "ThreadPoolV3.hpp"
#include "ThreadPoolV4.hpp"
#include "Task.hpp"
#include <cstring>
#include <memory>
int main()
{
ThreadPool<Task>* pool=ThreadPool<Task>::GetInstance();
srand((uint64_t)time(0));
pool->Init();
pool->Start();
pool->Check();
while(1)
{
int x=rand()%10;
int y=rand()%20;
char arr[]="+-*/%";
char op=arr[rand()%strlen(arr)];
pool->push(Task(x,y,op));
sleep(1);
}
return 0;
}
//Lock.hpp
//对锁进行一个封装,RAII
#pragma once
#include <pthread.h>
#include <iostream>
using namespace std;
class Lock_Guard
{
public:
Lock_Guard(pthread_mutex_t* mutex)
:_mutex(mutex)
{
pthread_mutex_lock(_mutex);
}
//构造函数的时候加锁,析构函数的时候解锁
~Lock_Guard()
{
pthread_mutex_unlock(_mutex);
}
private:
pthread_mutex_t* _mutex;
};
//Task.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include<string>
using namespace std;
class Task
{
public:
Task()
{}
Task(int x,int y,char op)
:_x(x),_y(y),_op(op)
{}
void operator()()
{
switch(_op)
{
case '+':
_result=_x+_y;
break;
case '-':
_result=_x-_y;
break;
case '/':
if(_y==0)
{
_code=-1;
break;
}
_result=_x/_y;
break;
case '*':
_result=_x*_y;
break;
case '%':
if(_y==0)
{
_code=-2;
break;
}
_result=_x%_y;
break;
default:
_code=-3;
break;
}
}
string to_productor()//打印productor生产的内容是什么
{
return to_string(_x)+_op+to_string(_y)+"=?";
}
string to_customer()//打印customer消费后的结果为什么
{
return to_string(_x)+_op+to_string(_y)+"="+to_string(_result)+";code="+to_string(_code);
}
private:
int _x;//操作数
int _y;//操作数
char _op;//计算方法
int _result=0;//计算结果
int _code=0;//计算后的返回码
};
//_code:
//-1 -> /出错
//-2 -> %出错
//-3 -> _op出错
//Thread.hpp
//自己写的Thread对象
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <vector>
#include <unistd.h>
#include <functional>
using namespace std;
using func_t=function<void*(void*)>;
enum Status
{
NEW,
RUNNING,
EXIT,
};
class Thread
{
public:
Thread(int num,func_t func,void* arg)
:_func(func),
_arg(arg)
{
char buff[1024];
snprintf(buff,sizeof(buff),"thread-%d",num);
_name+=buff;
_status=NEW;
}
void Run()
{
if(0!=pthread_create(&_tid,nullptr,RunHelper,this))
//这边又封装了一层,来让我们线程控制的函数能够让我们进行控制,为后续的线程池做铺垫
{
exit(-1);
}
_status=RUNNING;
}
static void* RunHelper(void* arg)
{
Thread* td=static_cast<Thread*>(arg);
(*td)();//这边也封装了一层
}
void* operator()()
{
return _func(_arg);
}
void Join(void)
{
int n=pthread_join(_tid,nullptr);
if(n!=0)
{
exit(-2);
}
_status=EXIT;
}
string ThreadName()
{
return _name;
}
pthread_t ThreadId()
{
if(_status==RUNNING)
{
return _tid;
}
else
{
return 0;
}
}
private:
string _name;//线程名
pthread_t _tid;//线程id
Status _status;//线程当前的一个状态
func_t _func;//线程执行的函数
void* _arg;//传给线程执行的函数参数
};
//退出码:
//-1-> pthread_create error
//-2-> pthread_join error
// ThreadPoolV4.hpp
// 此版本的线程池是使用的是自己写的Thread对象,同时增加了自己写的锁的封装,来实现RAII;同时增加单例模式
#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>
#include <vector>
#include <queue>
#include "Task.hpp"
#include "Thread.hpp"
#include "Lock.hpp"
using namespace std;
#define SIZE 5
template <class T>
class ThreadPool
{
private:
ThreadPool(int size = SIZE)
: // 这里不用把线程池空间先开好,不然会出现没有默认构造函数问题
_capacity(size)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_cond, nullptr);
}
ThreadPool(const ThreadPool&)=delete;
ThreadPool& operator=(const ThreadPool&)=delete;
//上述的操作保证只能通过GetInstance来创建对象->单例模式
public:
static ThreadPool<T>* GetInstance()
{
if(_ptr==nullptr)//加上来减少锁的一个消耗
{
Lock_Guard m(&_instance_mutex);
if(_ptr==nullptr)
{
_ptr=new ThreadPool<T>;
return _ptr;
}
}
return _ptr;
}
~ThreadPool()
{
for (int i = 0; i < _capacity; i++)
{
_pool[i].Join();
}
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
void Init() // 进行一个初始化,把我们的一个Thread对象放进线程池
{
for (int i = 0; i < _capacity; i++)
{
_pool.push_back(Thread(i, Routine, this));
}
}
void Start() // 我们的线程真正开始执行起来
{
for (int i = 0; i < _capacity; i++)
{
_pool[i].Run(); // 调用Thread对象的Run函数
}
}
void push(const T &task)
{
Lock_Guard m(&_mutex);//使用RAII
// pthread_mutex_lock(&_mutex);
_task.push(task);
pthread_cond_signal(&_cond); // 唤醒线程来执行task
// pthread_mutex_unlock(&_mutex);
}
static void *Routine(void *arg)
{
ThreadPool *tp = static_cast<ThreadPool *>(arg);
while (1)
{
T t;
// 所有的线程都在这里面运行,省去了创建和销毁的开销
{
Lock_Guard m(&tp->_mutex);//通过加上括号来实现RAII
// pthread_mutex_lock(&tp->_mutex);
while (tp->_task.empty()) // 使用while而不是if可以更安全,即使意外唤醒了
{
pthread_cond_wait(&tp->_cond, &tp->_mutex);
}
t = tp->_task.front(); // 获取任务
tp->_task.pop();
}
// pthread_mutex_unlock(&tp->_mutex);
t(); // 完成任务
cout << t.to_customer() << endl;
}
}
void Check()
{
for (int i = 0; i < _capacity; i++)
{
cout << _pool[i].ThreadName() << endl;
}
}
private:
vector<Thread> _pool; // 线程池
queue<T> _task; // 任务
int _capacity; // 线程池的大小
pthread_mutex_t _mutex; // 保护task的资源
pthread_cond_t _cond; // 在没有task时进行一个等待
static pthread_mutex_t _instance_mutex;//单例模式中使用
static ThreadPool<T>* _ptr;//单例模式中用到的唯一一个对象
};
template<class T>
ThreadPool<T>* ThreadPool<T>::_ptr=nullptr;
template<class T>
pthread_mutex_t ThreadPool<T>::_instance_mutex=PTHREAD_MUTEX_INITIALIZER;
# makefile
main:main.cc
g++ -o main main.cc -std=c++11 -lpthread
.PHONY:clean
clean:
rm -rf main
4.STL和线程安全
STL中的容器不是线程安全的,这是为了提高性能
智能指针中的unique_ptr不涉及线程安全,而shared_ptr中用到了引用计数变量,会有线程安全,因此基于原子操作(CAS)的方式保证shared_ptr高效
5.各种锁
悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁,当其他线程想要访问数据时,被挂起阻塞。
乐观锁:每次取数据时,总是乐观的认为数据不会被其他的数据修改,因此不上锁。但是在更新前,会判断其他数据在更新前有没有对数据进行修改,主要采用两种方式:版本号机制和CAS操作
自旋锁:不停地轮询而不是挂起等待,对于是自锁或挂起等待看的是访问临界资源要花费多长时间
6.读者写者问题
321原则:
3:三种关系,读者和读者(没关系);写写(互斥);读写(互斥,同步)
2:读者;写者
1:交易场所
cp的消费者之前是互斥,而rw问题中,读者之间没有关系是因为消费者会拿走数据,而读者不会
注意:写独占,读共享,读锁优先级要高,因此容易导致写者饥饿问题
🌸🌸Linux多线程(下)的知识大概就讲到这里啦,博主后续会继续更新更多Linux的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪