线程系列:
Linux–线程的熟悉(一)
Linux–线程的分离、线程库的地址关系的明白、线程的简朴封装(二)
线程的互斥:临界资源只能在同一时间被一个线程使用
生产斲丧模子
信号量
线程池(包含日志的表明)
线程安全
线程安全指的是在多线程情况下,某个操作或方法被多个线程并发实行时,能够保持数据的一致性和完整性,即不会由于多个线程的交替实行而导致数据被粉碎或步伐的行为不符合预期。
简朴来说,假如一个操作在多线程情况下实行时,能够包管数据的一致性和操作的原子性,那么这个操作就是线程安全的。
关键因素
- 原子性:操作或方法在实行过程中,要么全部完成,要么完全不实行,不会被其他线程的操作打断。
- 可见性:一个线程对共享变量的修改,能够立刻被其他线程看到。
- 有序性:步伐实行的序次按照代码的先后序次实行,不会由于重排序而导致数据庞杂。
题目来源
- 共享资源:多个线程访问同一个资源(如变量、文件等),假如没有适当的同步机制,就可能导致数据不一致。
- 竞态条件:两个或多个线程同时实行,且它们的实行序次和结果依靠于它们的相对实行速度,可能导致步伐行为不可猜测。
死锁
死锁是指两个或多个历程(线程)在实行过程中,因竞争资源或由于彼此通信而造成的一种阻塞现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相称候的历程(线程)称为死锁历程。
产生原因
- 竞争不可抢占资源:当系统中供多个历程共享的资源(如打印机、公用队列等)数目不敷以满足诸历程的必要时,会引起历程对资源的竞争而产生死锁。
- 竞争可斲丧资源:除了不可抢占资源外,对可斲丧资源(如内存)的争夺也可能引起死锁。
- 历程推进序次不妥:历程在运行过程中,假如推进序次不妥,也可能导致死锁的发生。
产生的须要条件
- 互斥条件:至少有一个资源被标志为独占,即一次只能被一个历程(线程)使用。
- 请求与保持条件:历程(线程)已经持有了至少一个资源,而且在期待获取其他历程(线程)持有的资源。
- 不可剥夺条件:已经分配给历程(线程)的资源不能被逼迫性地剥夺,只能由历程(线程)自己释放。
- 循环期待条件:存在一个历程(线程)的资源请求链,使得每个历程(线程)都在期待下一个历程(线程)所持有的资源。
预防方法
- 粉碎互斥条件:对于某些资源,可以允许多个历程(线程)同时访问,而不是独占资源。例如,使用读写锁取代互斥锁。
- 粉碎请求与保持条件:在申请新资源之前,释放已经持有的资源。只有在获得全部必要的资源后,才重新申请资源。
- 粉碎不可剥夺条件:允许操作系统逼迫性地剥夺某些历程(线程)所持有的资源,并将其分配给其他期待资源的历程(线程)。
- 粉碎循环期待条件:对资源举行全局排序,并按照序次申请资源,制止形成循环期待链。例如,可以使用银行家算法来制止死锁。
排除死锁的方法
- 资源剥夺法:挂起某些死锁历程,并抢占它的资源,将这些资源分配给其他的死锁历程。
- 打消历程法:逼迫打消部分或全部死锁历程并剥夺这些历程的资源,直至打破循环环路,使系统从死锁状态中摆脱出来。打消的原则可以按历程优先级和打消历程代价的高低举行。
- 历程回退法:让一个或多个历程回退到足以制止死锁的田地,历程回退时自愿释放资源而不是被剥夺。要求系统保持历程的历史信息,设置还原点。
银行家算法
在盘算机系统中,对于每种资源都设置一个最大需求量和当前可用量。当一个历程提出资源请求时,系统首先检查该历程是否满足其最大需求量限定,假如请求正当,则尝试分配资源给该历程并检查是否会导致系统进入不安全状态(即可能发生死锁),假如不会,则分配资源;否则,拒绝该历程的请求。
算法步骤
1.检查请求是否正当:
- 假如Request <= Need(请求的资源数不凌驾历程i还必要的资源数)且Request <= Available(请求的资源数不凌驾系统中现有的可用资源数),则请求正当,否则请求不正当,拒绝分配。
2.模拟分配资源:
- 假设为历程i分配资源,更新相干数据布局:
- Available = Available - Request
- Allocation = Allocation + Request
- Need = Need - Request
3.实行安全性算法:
安全性算法用于检查此次资源分配后,系统是否处于安全状态。详细步骤如下:
- 设置两个向量:Work(表现系统当前可提供给历程继承实行所必要的各类资源的数目,初值为Available)和Finish(表现系统是否有足够的资源分配给历程,使之完成运行,初值全为false)。
- 从历程聚集中寻找满足Finish = false且Need <= Work的历程,若找到,则实行以下操作:
– 历程i获得资源,可顺遂实行,完成释放所分配的资源:Work = Work + Allocation,Finish = true。
- 重复上述步骤,直到全部历程的Finish都为true(表现系统处于安全状态),或无法再找到满足条件的历程(表现系统处于不安全状态)。
4.根据检查结果决定是否真正分配资源:
- 假如安全性算法结果为系统处于安全状态,则真正举行资源分配;假如系统处于不安全状态,则打消此次模拟分配,规复原来状态,拒绝申请。
单例题目
在上一篇中,我们使用的线程池是可以无限定的创建的,假如每个任务或请求都创建一个新的线程池,那么当任务或请求量很大时,会导致创建大量的线程池实例,每个实例内部又可能包含多个线程,这将极大地浪费系统资源,包括内存和CPU时间。
使用单例模式可以确保整个应用或系统只存在一个线程池实例,这样全部的任务或请求都可以共享这个线程池中的线程,制止了资源的浪费;
单例的线程池
- #pragma once
- #include<iostream>
- #include<vector>
- #include<queue>
- #include<pthread.h>
- #include"Thread.hpp"
- #include"Log.hpp"
- #include"LockGuard.hpp"
- using namespace ThreadMdule;
- using namespace std;
- const static int gdefaultthreadnum=3;//默认线程池的线程数
- template <class T>
- class ThreadPool
- {
- public:
- ThreadPool(int threadnum=gdefaultthreadnum) :_threadnum(threadnum),_waitnum(0),_isrunning(false)
- {
- pthread_mutex_init(&_mutex,nullptr);
- pthread_cond_init(&_cond,nullptr);
- LOG(INFO,"ThreadPool COnstruct.");
- }
- //禁用拷贝赋值
- ThreadPool(const ThreadPool<T>&) = delete;
- ThreadPool<T>& operator=(const ThreadPool<T>&) = delete;
- //单例:懒汉模式
- static ThreadPool<T>* GetInstance()
- {
- //再次判断,减少获取单例的加锁成本,保证线程安全
- if(_instance==nullptr)
- {
- LockGuard lockguard(&_lock);
- if(_instance==nullptr)
- {
- _instance= new ThreadPool<T>();
- _instance->InitThreadPool();
- _instance->Start();
- LOG(DEBUG,"创建单例线程池");
- return _instance;
- }
- }
- LOG(DEBUG,"获取单例线程池");
- return _instance;
- }
- //各个线程独立的任务函数
- void HandlerTask(string name)
- {
- LOG(INFO,"%s is running...",name.c_str());
- while(true)
- {
- LockQueue();//开启保护
- //等到有任务时才退出循环执行下列语句
- while(_task_queue.empty()&&_isrunning)
- {
- _waitnum++;
- ThreadSleep();
- _waitnum--;
- }
- //当任务队列空并且线程池停止时线程退出
- if(_task_queue.empty()&&!_isrunning)
- {
- UnlockQueue();
- cout<<name<<" quit "<<endl;
- sleep(1);
- break;
- }
- //1.任务队列不为空&&线程池开启
- //2.任务队列不为空&&线程池关闭,直到任务队列为空
- //所以,只要有任务,就要处理任务
- T t=_task_queue.front();//取出对应任务
- _task_queue.pop();
- UnlockQueue();
- LOG(DEBUG,"%s get a task",name.c_str());
- //处理任务
- t();
- LOG(DEBUG,"%s handler a task,result is: %s",name.c_str(),t.ResultToString().c_str());
- }
- }
- //线程池中线程的构建
- void InitThreadPool()
- {
- for(int i=0;i<_threadnum;i++)
- {
- string name="thread-"+to_string(i+1);
- _threads.emplace_back(bind(&ThreadPool::HandlerTask,this,placeholders::_1),name);
- LOG(INFO,"init thread %s done",name.c_str());
- }
- _isrunning=true;
- }
- //线程池的启动
- void Start()
- {
- for(auto& thread:_threads)
- {
- thread.start();
- }
- }
- //线程池停止
- void Stop()
- {
- LockQueue();
- _isrunning=false;
- ThreadWakeupAll();
- UnlockQueue();
- }
- void Wait()
- {
- for(auto& thread:_threads)
- {
- thread.Join();
- LOG(INFO,"%s is quit...",thread.name().c_str());
- }
- }
- //将任务入队列
- bool Enqueue(const T& t)
- {
- bool ret=false;
- LockQueue();
- if(_isrunning)
- {
- _task_queue.push(t);
- //如果有空闲的线程,那么唤醒线程让其执行任务
- if(_waitnum>0)
- {
- ThreadWakeup();
- }
- LOG(DEBUG,"enqueue task success");
- ret=true;
- }
- UnlockQueue();
- return ret;
- }
- ~ThreadPool()
- {
- pthread_mutex_destroy(&_mutex);
- pthread_cond_destroy(&_cond);
- }
- private:
- void LockQueue()
- {
- pthread_mutex_lock(&_mutex);
- }
- void UnlockQueue()
- {
- pthread_mutex_unlock(&_mutex);
- }
- void ThreadSleep()
- {
- pthread_cond_wait(&_cond, &_mutex);
- }
- void ThreadWakeup()
- {
- pthread_cond_signal(&_cond);
- }
- void ThreadWakeupAll()
- {
- pthread_cond_broadcast(&_cond);
- }
- int _threadnum;//线程数
- vector<Thread> _threads;//存储线程的vector
- queue<T> _task_queue;//输入的任务队列
- pthread_mutex_t _mutex;//互斥锁
- pthread_cond_t _cond;//条件变量
- int _waitnum;//空闲的线程数
- bool _isrunning;//表示线程池是否启动
- //单例模式
- static ThreadPool<T>* _instance;
- static pthread_mutex_t _lock;
- };
- template<typename T>
- ThreadPool<T>* ThreadPool<T>::_instance=nullptr;
- template<typename T>
- pthread_mutex_t ThreadPool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |