当前位置: 首页 > news >正文

自己做本地网站济南网站建设xywlcn

自己做本地网站,济南网站建设xywlcn,wordpress自定义title,wordpress官方主题教程线程池单例模式STL,智能指针和线程安全其他常见的各种锁读者写者问题 1.线程池2.线程安全的单例模式3.STL,智能指针和线程安全4.其他常见的各种锁4.读者写者问题 喜欢的点赞#xff0c;收藏#xff0c;关注一下把#xff01; 1.线程池 目前我们学了挂起等待锁、条件变量、信… 线程池单例模式STL,智能指针和线程安全其他常见的各种锁读者写者问题 1.线程池2.线程安全的单例模式3.STL,智能指针和线程安全4.其他常见的各种锁4.读者写者问题 喜欢的点赞收藏关注一下把 1.线程池 目前我们学了挂起等待锁、条件变量、信号量、生产者消费者模型那我们就根据这些写一个线程池 线程池:一种线程使用模式。线程过多会带来调度开销进而影响缓存局部性和整体性能。而线程池维护着多个线程等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。线程池的应用场景 需要大量的线程来完成任务且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务使用线程池技 术是非常合适的。因为单个任务小而任务数量巨大你可以想象一个热门网站的点击次数。 但对于长时间的任务比如一个 Telnet连接请求线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。 对性能要求苛刻的应用比如要求服务器迅速响应客户请求。 接受突发性的大量请求但不至于使服务器因此产生大量线程的应用。突发性大量客户请求在没有线程池情 况下将产生大量线程虽然理论上大部分操作系统线程数目最大值不是问题短时间内产生大量线程可能使内存到达极限 出现错误. 线程池的种类线程池示例 创建固定数量线程池循环从任务队列中获取任务对象 获取到任务对象后执行任务对象中的任务接口 一般我们都是来了一个任务我们才创建一个线程这种模式在处理任务虽然没有问题但是这样做可能会导致效率降低因为创建线程也是有成本的。 就像我们在学习STL的时候看到vector扩容是按1.5倍扩容的本来我只需要10个字节可能它会给你15个字节多出来的空间当你后序还想扩容可能者这多出来空间就够了不需要再扩容了因为扩容也是要花费时间的。这种思想就是池化技术。 未来我们先提前创建出一批线程让这些线程去扫描任务队列中有没有任务有任务让这些线程去拿任务然后处理任务。没任务就全部休眠而不推出唤醒一个线程的成本可比创建一个线程的成本要低的多 。 因此我们的模型如下 这就是我们的线程池 当看到这个图时如果把以前的知识学的扎实这份代码其实自己就会写了。因为它就是典型的生产者消费者模式 接下来我们把线程池写出来。 线程池线程池首先我们需要有线程。首先把创建线程写出来 #pragma once #includeiostream #includestring #includepthread.h #includefunctionalclass Thread {typedef std::functionvoid*(void*) func_t; private://类内成员有隐藏的this指针,不加static就会报错!//但是我们又需要this指针,调用类的成员变量,因此把this传过来static void* start_routine(void* args){Thread* _thisstatic_castThread*(args);//安全进行类型转换return _this-_func(_this-_args);//调用回调函数,不这样写也可以再写一个类内函数在调用} public:Thread(){char namebuffer[64];snprintf(namebuffer,sizeof namebuffer,thread-%d,_number);_namenamebuffer;}//为什么这里参数不放在构造函数//因为我们等会想线程运行的时候,知道是那个线程在运行把_name也一起传过去void start(func_t func,void* args){_funcfunc;_argsargs;//这个函数不认识C的function类,因此我自己写一个函数pthread_create(_tid,nullptr,start_routine,this);}void join(){pthread_join(_tid,nullptr);}//线程名std::string threadname(){return _name;}~Thread(){}private:std::string _name;//线程名func_t _func;//回调函数void* _args;//回调函数参数pthread_t _tid;//线程IDstatic int _number; };int Thread::_number1;在把放的任务写一下 #pragma once #includeiostream #includefunctional #includestringclass Task {typedef std::functionint(int,int,char) func_t; public:Task(){};Task(int x,int y,char op,func_t func):_x(x),_y(y),_op(op),_func(func){}std::string operator()(){int result_func(_x,_y,_op);char buffer[64];snprintf(buffer,sizeof buffer,%d %c %d %d,_x,_op,_y,result);return buffer;}std::string toTaskString(){char buffer[64];snprintf(buffer,sizeof buffer,%d %c %d ?,_x,_op,_y);return buffer;}private:int _x;int _y;char _op;func_t _func; };int mymath(int x, int y, char op) {int result 0;switch (op){case :resultxy;break;case -:resultx-y;break;case *:resultx*y;break;case /:{if(y 0){std::coutdiv zero errorstd::endl;result-1;}elseresultx/y;}break;case %:if(y 0){std::coutmod zero errorstd::endl;result-1;}elseresultx%y;break;default:break;}return result; }在想线程池的代码之前我们要先想好需要创建几个线程这些线程怎么管理任务放在哪。 因此我们需要一个创建几个线程的变量创建好的线程我们打算用vector进行管理任务我们打算放在队列中。 还有一件事情当线程在队列中拿的时候不能放任务放任务的时候线程不能拿并且我们还想让线程有序的进行拿任务。 因此我们还需要一把锁一个条件变量。 #pragma once #includeThread.hpp #includeTask.hpp #includevector #includequeueusing namespace std; const int maxcap3;//声明 templateclass T class ThreadPool;templateclass T class ThreadData { public:ThreadData(ThreadPoolT* poolthis,const string name):_poolthis(poolthis),_name_(name){}~ThreadData(){} public:ThreadPoolT* _poolthis;string _name_; };templateclass T class ThreadPool { private://线程调用的处理任务函数static void* handTask(void* args){ThreadDataT* tdstatic_castThreadDataT*(args);while(true){//这里我们写了一些函数调用,也可以每个都加this指针调用//放任务之前加锁td-_poolthis-threadlock();while(td-_poolthis-IsQueueEmpty())//任务队列空线程就等待{td-_poolthis-threadwait();}//取任务Task t;td-_poolthis-pop(t);//注意一定要先解锁,在处理任务!不然串行处理任务一点意义都没有!!td-_poolthis-threadunlock();//线程并行处理任务couttd-_name_ 处理完了任务: t()endl;}} private:void threadlock(){pthread_mutex_lock(_lock);}void threadunlock(){pthread_mutex_unlock(_lock);}void threadwait(){pthread_cond_wait(_cond,_lock);}void pop(T* out){*out_task_queue.front();_task_queue.pop();}bool IsQueueEmpty(){return _task_queue.empty();}public:ThreadPool(int capmaxcap):_cap(maxcap){//初始化锁,条件变量pthread_mutex_init(_lock,nullptr);pthread_cond_init(_cond,nullptr);//创建线程for(int i0;i_cap;i){_threads.push_back(new Thread());//创建线程并放在vector里}}//启动线程//在Thread里说过,想把线程名也传过去,但是回调函数只有一个函数//而这函数我们写在类里面必须要加一个static,导致没有this指针,而使用类内成员需要this指针//因此我们写个类把线程名和this都传过去void run(){for(auto thread:_threads){ThreadDataT* tdnew ThreadDataT(this,thread-threadname());thread-start(handTask,td);}}//任务队列放任务void push(const T in){//保证放任务是安全的,所以先加锁pthread_mutex_lock(_lock);_task_queue.push(in);pthread_cond_signal(_cond);//队列中有任务就唤醒等待的线程去取任务pthread_mutex_unlock(_lock);}~ThreadPool(){//销毁锁,条件变量pthread_mutex_destroy(_lock);pthread_cond_destroy(_cond);}private:int _cap;//线程个数vectorThread* _threads;//线程放在vector里进行管理queueT _task_queue;//任务队列pthread_mutex_t _lock;pthread_cond_t _cond; };看运行结果线程池内的线程是有序的在处理任务符合我们的要求。 前面我们锁的封装我们也不写过好这里我们也用一用 #pragma once #includeiostream #includepthread.hclass Mutex { public:Mutex(pthread_mutex_t* lock):_lock(lock){pthread_mutex_init(_lock,nullptr);}void lock(){pthread_mutex_lock(_lock);}void unlock(){pthread_mutex_unlock(_lock);}~Mutex(){pthread_mutex_destroy(_lock);} private:pthread_mutex_t* _lock; };class LockGuard { public:LockGuard(pthread_mutex_t* lock):_mutex(lock){_mutex.lock();}~LockGuard(){_mutex.unlock();} private:Mutex _mutex; };在修改一下线程池有关处理任务的代码 //线程调用的处理任务函数static void* handTask(void* args){ThreadDataT* tdstatic_castThreadDataT*(args);Task t;while(true){//RAII 风格加锁{//构造时自动加锁,析构时自动结束//局部变量生命周期这个代码块LockGuard lockguard(td-_poolthis-mutex());while (td-_poolthis-IsQueueEmpty()){td-_poolthis-threadwait();}td-_poolthis-pop(t);}cout td-_name_ 处理完了任务: t() endl;}} private:void threadlock(){pthread_mutex_lock(_lock);}void threadunlock(){pthread_mutex_unlock(_lock);}void threadwait(){pthread_cond_wait(_cond,_lock);}void pop(T* out){*out_task_queue.front();_task_queue.pop();}bool IsQueueEmpty(){return _task_queue.empty();}pthread_mutex_t* mutex(){return _lock;}这个代码需要大家自己写一写体会一下才能加深知识的理解这里没有新的内容都是以往我们学的知识! 2.线程安全的单例模式 什么是单例模式 单例模式是一种 “经典的, 常用的, 常考的” 设计模式. 什么是设计模式 IT行业这么火, 涌入的人很多. 俗话说林子大了啥鸟都有. 大佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖大佬的后腿, 于是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是 设计模式 单例模式的特点 某些类, 只应该具有一个对象(实例), 就称之为单例. 例如 一个男人只能有一个媳妇. 在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据. 饿汉实现方式和懒汉实现方式 【洗碗的例子】 吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭. 吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式. 就如在C定义一些全局变量或静态变量这些变量在全局数据区提前给我们定义好了。当我们的可执行程序加载到内存的时候在程序还没有运行之前我们都要先把当前的对象立马创建出来就是我还没用就先把它创建出来了像这种模式就是饿汉模式当我用的时候不用创建直接拿来就用 使用之前创建好好了—饿汉模式 当创建的时候先不创建在用的时在new或malloc创建 —懒汉模式 懒汉方式最核心的思想是 “延时加载”. 从而能够优化服务器的启动速度 问 在我们进行new或malloc申请堆空间时OS是不是在你申请的时侯就把空间给你了 答 肯定不是你申请和你未来使用肯定还有很长时间作为OS来说把空间给你了但是你不会立即使用你不立即使用这段给你的空间不就处于闲置状态了嘛对于OS来说为什么要做这个事情呢 所以你在new或malloc的时候申请的堆空间就没有在物理内存给你开辟而只是在虚拟地址空间上把你的地址扩大一点然后把起始虚拟地址返回未来在你真正想对这块空间给你写入时那么OS底层才会触发缺页中断然后OS才会在物理内存重新开辟空间然后重新构建你曾经申请的虚拟地址和物理内存之间的映射关系 所谓缺页中断就是一旦访问内存时发现虚拟地址到页表之间转化到物理内存没有对应的映射关系那么此时OS就把你的工作停下来开始执行OS的代码就相当于在内存中给你把空间找到找到之后修改你的页表重新构建映射关系就好了。 我们平时调用new和malloc在OS层面上OS也给我们叫做延时开辟。 饿汉方式实现单例模式 template typename T class Singleton {static T data;//直接定义一个单例对象 public:static T* GetInstance() {return data;//未来获取的时直接获取} };饿汉在设置的时候只需要将类的拷贝构造赋值等等能构造出对象的这些语句全都delete掉然后在类中用当前构造函数中定义出静态对象就可以了。静态方法和全局变量在代码加载到内存在语言的角度在加载到内存的时候对象就已经创建出来跟你定义全局变量一样在系统的角度定义的全局变量不就在地址空间中的全局数据区吗所以这个对象在它加载的时候这个对象就已经有了 相当于只要是全局的或者静态的在进程加载到内存时这个单例就已经有了。 加载时立马就创建这就是饿汉如果单例很大启动时间就很久。 懒汉方式实现单例模式 template typename T class Singleton {static T* inst; public:static T* GetInstance() {if (inst NULL) {inst new T();}return inst;} };定义一个指针先不创建你的对象当在用的时候在创建出来。 上面存在一个严重的问题, 线程不安全. 第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例. 但是后续再次调用, 就没有问题了 懒汉方式实现单例模式(线程安全版本) // 懒汉模式, 线程安全 template typename T class Singleton {volatile static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.static std::mutex lock; public:static T* GetInstance() {if (inst NULL) { // 双重判定空指针, 降低锁冲突的概率, 提高性能.lock.lock(); // 使用互斥锁, 保证多线程情况下也只调用一次 new.if (inst NULL) {inst new T();}lock.unlock();}return inst;} };注意事项: 加锁解锁的位置双重 if 判定, 避免不必要的锁竞争volatile关键字防止过度优化 下面将我们的线程池改成懒汉模式的单例 #pragma once #includeThread.hpp #includeTask.hpp #includevector #includequeue #includeMutex.hppusing namespace std; const int maxcap3;//声明 templateclass T class ThreadPool;templateclass T class ThreadData { public:ThreadData(ThreadPoolT* poolthis,const string name):_poolthis(poolthis),_name_(name){}~ThreadData(){} public:ThreadPoolT* _poolthis;string _name_; };templateclass T class ThreadPool { private://线程调用的处理任务函数static void* handTask(void* args){ThreadDataT* tdstatic_castThreadDataT*(args);Task t;while(true){//RAII 风格加锁{//构造时自动加锁,析构时自动结束//局部变量生命周期这个代码块LockGuard lockguard(td-_poolthis-mutex());while (td-_poolthis-IsQueueEmpty()){td-_poolthis-threadwait();}td-_poolthis-pop(t);}cout td-_name_ 处理完了任务: t() endl}}private:void threadlock(){pthread_mutex_lock(_lock);}void threadunlock(){pthread_mutex_unlock(_lock);}void threadwait(){pthread_cond_wait(_cond,_lock);}void pop(T* out){*out_task_queue.front();_task_queue.pop();}bool IsQueueEmpty(){return _task_queue.empty();}pthread_mutex_t* mutex(){return _lock;}//单例不是没有例,构造函数不能去掉,放在private就好了ThreadPool(int capmaxcap):_cap(maxcap){//初始化锁,条件变量pthread_mutex_init(_lock,nullptr);pthread_cond_init(_cond,nullptr);//创建线程for(int i0;i_cap;i){_threads.push_back(new Thread());//创建线程并放在vector里}}//去掉赋值,拷贝构造void operator(const ThreadPool) delete;ThreadPool(const ThreadPool) delete;public://启动线程//在Thread里说过,想把线程名也传过去,但是回调函数只有一个函数//而这函数我们写在类里面必须要加一个static,导致没有this指针,而使用类内成员需要this指针//因此我们写个类把线程名和this都传过去void run(){for(auto thread:_threads){ThreadDataT* tdnew ThreadDataT(this,thread-threadname());thread-start(handTask,td);}}//任务队列放任务void push(const T in){//保证放任务是安全的,所以先加锁pthread_mutex_lock(_lock);_task_queue.push(in);pthread_cond_signal(_cond);//队列中有任务就唤醒等待的线程去取任务pthread_mutex_unlock(_lock);}~ThreadPool(){//销毁锁,条件变量pthread_mutex_destroy(_lock);pthread_cond_destroy(_cond);}//获取单例//成员函数可以调用静态成员和静态成员函数,反之不行ThreadPoolT* getInstance(){//线程不安全if(tp nullptr){tpnew ThreadPoolT();}return tp;}private:int _cap;//线程个数vectorThread* _threads;//线程放在vector里进行管理queueT _task_queue;//任务队列pthread_mutex_t _lock;pthread_cond_t _cond;static ThreadPoolT* tp; };templateclass T ThreadPoolT* ThreadPoolT::tpnullptr;为什么会报错 原因是因为虽然类内成员函数能够访问静态成员变量但是getInstance是一个成员函数里面有this指针。 这个成员函数既属性这个类又属于对象未来我们只想获取的单例只属于类因此我们需要给这个成员函数前面加个static那就只属于类了。 首次获取不存在就new一个否则直接返回 #includeThreadPool.hpp #includeunistd.hint main() {//一大堆代码...//ThreadPoolTask* tpnew ThreadPoolTask();//tp-run();//获取单例直接run起来ThreadPoolTask::getInstance()-run();//用的时候才创建int x,y;char op;while(true){cout请输入第一个数据#;cinx;cout请输入第二个数据#;ciny;cout请输入要进行的操作#;cinop;Task t(x,y,op,mymath);ThreadPoolTask::getInstance()-push(t);sleep(1);}return 0; }这就是懒汉模式的单例。 但是这种方式是线程不安全的tp本身就是一份公共资源如果两个线程同时调用, 可能会创建出两份 T 对象的实例。因此我们需要加锁。 单例完整代码 #pragma once #include Thread.hpp #include Task.hpp #include vector #include queue #include Mutex.hpp #include mutexusing namespace std; const int maxcap 3;// 声明 template class T class ThreadPool;template class T class ThreadData { public:ThreadData(ThreadPoolT *poolthis, const string name) : _poolthis(poolthis), _name_(name){}~ThreadData(){}public:ThreadPoolT *_poolthis;string _name_; };template class T class ThreadPool { private:// 线程调用的处理任务函数static void *handTask(void *args){ThreadDataT *td static_castThreadDataT *(args);Task t;while (true){// RAII 风格加锁{// 构造时自动加锁,析构时自动结束// 局部变量生命周期这个代码块LockGuard lockguard(td-_poolthis-mutex());while (td-_poolthis-IsQueueEmpty()){td-_poolthis-threadwait();}td-_poolthis-pop(t);}cout td-_name_ 处理完了任务: t() endl;}}private:void threadlock() { pthread_mutex_lock(_lock); }void threadunlock() { pthread_mutex_unlock(_lock); }void threadwait() { pthread_cond_wait(_cond, _lock); }void pop(T *out){*out _task_queue.front();_task_queue.pop();}bool IsQueueEmpty() { return _task_queue.empty(); }pthread_mutex_t *mutex(){return _lock;}// 单例不是没有例,构造函数不能去掉,放在private就好了ThreadPool(int cap maxcap) : _cap(maxcap){// 初始化锁,条件变量pthread_mutex_init(_lock, nullptr);pthread_cond_init(_cond, nullptr);// 创建线程for (int i 0; i _cap; i){_threads.push_back(new Thread()); // 创建线程并放在vector里}}// 去掉赋值,拷贝构造void operator(const ThreadPool ) delete;ThreadPool(const ThreadPool ) delete;public:// 启动线程// 在Thread里说过,想把线程名也传过去,但是回调函数只有一个函数// 而这函数我们写在类里面必须要加一个static,导致没有this指针,而使用类内成员需要this指针// 因此我们写个类把线程名和this都传过去void run(){for (auto thread : _threads){ThreadDataT *td new ThreadDataT(this, thread-threadname());thread-start(handTask, td);}}// 任务队列放任务void push(const T in){// 保证放任务是安全的,所以先加锁pthread_mutex_lock(_lock);_task_queue.push(in);pthread_cond_signal(_cond); // 队列中有任务就唤醒等待的线程去取任务pthread_mutex_unlock(_lock);}~ThreadPool(){// 销毁锁,条件变量pthread_mutex_destroy(_lock);pthread_cond_destroy(_cond);}// 获取单例// 成员函数可以调用静态成员和静态成员函数,反之不行static ThreadPoolT *getInstance(){// 虽然没有并发问题了,但是还有一个小问题// 未来每一个线程进来都要lock,unlock//因此在外面再加一个if判断,未来只要第一次实例化之后就不需要再加锁解锁了//大家就可以并发了if (tp nullptr){_singlock.lock();if (tp nullptr){tp new ThreadPoolT();}_singlock.unlock();}return tp;}private:int _cap; // 线程个数vectorThread * _threads; // 线程放在vector里进行管理queueT _task_queue; // 任务队列pthread_mutex_t _lock;pthread_cond_t _cond;static ThreadPoolT *tp;// c11的锁static std::mutex _singlock; };template class T ThreadPoolT *ThreadPoolT::tp nullptr;template class T mutex ThreadPoolT::_singlock; 3.STL,智能指针和线程安全 STL中的容器是否是线程安全的? 不是. 原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全. 智能指针是否是线程安全的? 对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题. 对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数. 4.其他常见的各种锁 悲观锁在每次取数据时总是担心数据会被其他线程修改所以会在取数据前先加锁读锁写锁行锁等当其他线程想要访问数据时被阻塞挂起。乐观锁每次取数据时候总是乐观的认为数据不会被其他线程修改因此不上锁。但是在更新数据前会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式版本号机制和CAS操作。CAS操作当需要更新数据时判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败失败则重试一般是一个自旋的过程即不断重试。自旋锁公平锁非公平锁 这里主要把自旋锁说一说 像以前用的互斥锁或者是信号量如果申请锁失败了对应的进程就会被挂起相当于一种阻塞状态或者挂起状态这种我们一般称为挂起等待锁。 我们一直没有谈论一个问题下面说一个小故事来谈论这个问题。 场景一 张三和李四是好朋友李四是一个学霸喜欢做笔记。马上就期末考试了张三想找李四借操作系统的笔记学一下于是就是李四打电话说请他吃饭等会一起区自习室学习。作为张三的好朋友李四同意了但是李四说我正在看这本书还要一小时才能看完你这楼下等等我把。张三有求于李四于是就同意了那我就在楼下等等你把。等到20分钟之后李四还没有下来张三着急了又给李四打电话问李四你下来没我在楼下等你呢李四说还不行还要再等会张三说行就把电话挂了又等了一会等了一会又很着急又给李四打电话李四你下来没李四说再等等就好了一个小时内张三给李四打了上百个电话最后一次张三给李四打电话李四说好了好了现在就下来。等下来之后张三李四先去吃饭然后去自习室学习。 场景二 第二天张三顺利考完操作系统之后觉得考的还行然后当天中午张三又跑到李四楼下给李四打电话说昨天你那笔记我们俩用的真不错马上就要考数据结构了你能不能把数据结构的笔记借我用一下李四说我正在楼上看书你能不能再楼下等我一个小时这时张三就想昨天把我等的太费劲了能不能这样你忙你的我去上会网你块好了你给我打电话打电话之后我在回来我就在你宿舍楼下等你然后一块去吃饭再去自习。李四说这个注意不错这样你也不用给我频繁打电话打扰我这时张三就很开始就去网吧上网去了大约过了4、50分钟李四给张三打电话说张三你回来把到我楼下我看好书了张三就回去了跟李四就一块吃饭然后仔细了。 故事讲完了下面有些问题 第二个场景张三去网吧上网等待李四翻译过来就是当前线程尝试去申请锁一看上一个线程可能要在临界资源里面待的时间特别久就将自己挂起等待。 第一个场景李四好了没好了就下来没好张三就挂电话李四好了没好了就下来没好张三就挂电话不断不断的打电话询问条件是否就绪这种状态我们称之为自旋(就相遇于轮询)。 问题当然不是这个 问 是什么决定了最终的等待方式 答 我们要等待的时长问题 一个成功申请临界资源的线程在临界区待的时间长短就决定了我们是选择挂起等待还是自旋 未来多线程并发访问共享资源的时候如果在临界区里面本来待的时间就很久你采用轮询这种方案去检测就不合适我们选择挂起等待的方式 那这个时间如何定义呢 首先这个问题没有标准答案长或短是对比出来的。如果拘泥于代码方面我们是得不到正确答案的。评估时间长短有两种方案。方案一结合应用场景。比如说当前一份代码不管是临界区还是非临界区都充满了IO此时你可以选择挂起等待或者自旋这看程序员自己但是一般在临界区里面诸如复杂计算、IO操作、等待某种软件条件等等大概率都是要挂起等待。如果在临界区里面就是非常简单的内存操作如抢票代码很短的很快的就能执行完的我们就可以采用自旋方案。但是大部分时候自旋方案我们很少去选主要因为挂起等待虽然要慢一点自旋看起要快一些但是自旋一旦你评估时间失误那么它会大量消耗CPU资源比如在挂起等待出现死锁大不了我们彼此之间不推荐但是在自旋出现死锁所以的执行都去疯狂的去自旋检测锁的状态但是没人释放最后导致我们的CPU瞬间就满了所以自旋锁相对比较危险的。方案二都用分别测试效果跑一段时间那个效率高就选那个。 下面我们看看自旋接口 pthread_spinlock_t //自旋锁类型它的用法和互斥锁一样。 初始化销毁 加锁 解锁 下来可以用一下。 4.读者写者问题 在编写多线程的时候有一种情况是十分常见的。那就是有些公共数据修改的机会比较少。相比较改写它们读的机会反而高的多。通常而言在读的过程中往往伴随着查找的操作中间耗时很长。给这种代码段加锁会极大地降低我们程序的效率。那么有没有一种方法可以专门处理这种多读少写的情况呢 有那就是读写锁。 讲个小故事理解一下。 你们班级有一名女生字写的非常好图也画的特别好因此班主任让她去出黑板报当女孩正在出的时候大家就跑过去看盲猜正在画的是什么一个同学说小花画的是一条蛇另一个同学说不对小花画的是一条龙另一个同学也说不对小花画的是一条蜗牛大家在哪里窃窃私语的讨论小花画的是什么结果小花最后人家画的是一张世界地图。当小花正在出黑板报的时候其他人在去读取读到的都是内容局部性东西最终猜的都不对拿的数据都不正确。为了保证大家读的都是完整的班里出了规定当小花出黑板报的时候大家都不能看小花要么不出要么就出完出完再来读。小花出完了大家都去看进行讨论。当大家都在谈论的时候会不会规定这一批都去看黑板把的人大家都排好队一个一个来看会不会规定谁先来谁先看其他人都把眼睛闭上。当然不会这样规定。其中大部分情况都是一块看。在出黑板报的时候要么一个人出要么两个人出一人一半但是一个人在出的时候不会让另一个人也出还是要一个一个人来。 现在把这个例子放在一边我们谈一谈读者写者问题然后把两者结合一块理解 读者写者问题我们的分析思路依旧遵守生产者消费者模型321原则。 出黑板报的小花就是写者这些看的人就是读者出黑板版本质就是一个读者写者问题。 3种关系 写者写者之间不能说你正在黑板写字另一个人要把字擦掉要画画因此写者写者之间是典型的互斥关系。 读者写者之间就像刚才例子小花正在画其他人就来猜了这样读不到完整的。另外黑板版画好了大家再读的时候小花就给擦掉了这也不对。所以这是典型的互斥。并且还要一种这个黑板报是放假之前出的在开学之前又给擦了小花出的黑板版没人看是不是没有意义。所以写者写的数据没人读你又写了也没有什么意思。同样写者写的数据读者多了很长时间这个数据都陈旧了需要更新了这时是不是该让写者来写。所以读者和写者也有一定的同步关系。 读者读者之间什么关系 读者和读者没有关系彼此之间不需要互斥也不需要同步 难道小花出好黑板报大家需要排好队一个一个去看吗我能看你不能看根本没有这样的规定。所以读者读者之间没有关系你读你的我读我的大家之间互不影响 读者和读者之间为什么没有关系的本质就是 读者写者模型VS生产者和消费者模型的本质区别是什么 写者和生产者是一样主要就是读者和消费者的区别。消费者会拿走数据读者不会。 那什么场景下会用到这个读者写者模型呢 一次发布很长时间不做修改大部分时间都是被读取 如写的博客发出来之后除非要做修改大部分时间都是被读的。大部分时间都是在读取少量的时间在进行写入 下面见见相关接口 pthread_rwlock_t //读写锁初始化销毁 读加锁 未来读的角色和写的角色是两种角色读者采用读加锁的方式 写加锁 解锁 无论是读者还是写者最后不想用了都采用这个方案进行解锁 现在就有个问题读写者原理很清楚了但是读写锁是怎么做到给读加锁写加锁的呢 不过目前我们知道了在任意一个时刻只允许一个写者写入但是可能允许多个读者读取(写者阻塞)。 写者在写的时候不允许其他写者写也不允许读着读而读者在读的时候允许其他读者一起读但不允许写着写。 读者写者使用的是不同接口进行加锁它内部是怎么实现的下面写一点伪代码帮助理解 如果写者先来申请锁成功了在它正在写入时读者来了读者肯定也是第一个来的满足if条件要把写者锁关掉但是写者已经申请锁成功了读者只能在if内部lock阻塞等待。等到写者写完解锁成功了读者被唤醒然后继续往下运行。 如果读者先来进来就把写者的锁关闭其他读者进来就不会在给写者加锁了。只会无脑等到退出无脑- -最后一个人再把写锁解开。在读者正在读的时候写者申请锁就不可能成功。 为什么解锁是同一个函数加锁两个用的是不同函数。 pthread_rwlock_t //读写锁因为我们的读写锁是一个结构体所以它的内部可能包含了对应的读锁写锁还有读计数器。然后再进行对应的操作时使用特定的一套方案进行相关的操作。 最后再说一点读者写者中出现写者饥饿问题很正常因为本来就是大部分时间都是在读取少量的时间在进行写入。第一写入线程少第二写者行为很少人家可能读了一万次你才写一次所以凭什么让你先写所以大部分人家去读取让你去饥饿是很正常的。读者写者问题天然就具备写者饥饿的现象。读者来了让读者优先去读这是我们默认的读者优先。读者优先就是读者持续的来就让写者一直等。上面那批接口默认就是读者优先的。 就是想让写者优先也是有场景的比如说10个读者1个写者前5个读者已经读了后面陆陆续续还要第6个第7个读者再来的路上有一次读者和写者一起来了按照以前读者优先就让读者先。如果是写者优先写者拦不住那些已经进去的但是可以拦住那些还没有进去的这些还没有进去的线程先别进去凡是在我写者来的时间点划分前面读者你先读后面的读者等一等等前面读者读完了都走了先让我们写者进去写完之后你们在读。 设置读写优先 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 写者优先但写者不能递归加锁 */到目前为止系统编程我们就学完啦真不容易。再写Linux有关博客就是我们的网络编程了后面再见
http://www.hkea.cn/news/14439269/

相关文章:

  • 做网站建设需要做哪些工作常用的品牌策划公司
  • 可以在线做护理题的网站wordpress 防止采集
  • 深圳正规网站建设免费推广的方式
  • 石家庄做网站最好的公司最好的网络推广方式
  • 门户网站建设与运行情况良好中国水运建设行业协会网站
  • 遂溪手机网站建设公司wordpress使用指南
  • 网站好的案例怎么编写程序
  • 网站网上预定功能怎么做网店运营与推广
  • 现货黄金什么网站可以做直播宝应网站设计
  • 中国建设银行客户端下载官方网站广州头条新闻最新
  • 在网上怎么建立自己的网站建设可以聊天的网站
  • 做网站有没有效果WordPress 4.8加速
  • 做微信小程序和网站那个简单公司网站建设进度
  • 高清无版权网站wordpress与阿里服务器区分
  • 网络营销企业网站优化宁宁网seo
  • hexo框架做网站河南做网站推广
  • 宿州网站建设多少钱cp网站开发是什么
  • 河南互助网站建设公司注册网上申报流程
  • 平谷做网站wordpress开发环境搭建
  • 做网站都可以做什么厦门外贸网站建设
  • 网站建设捌金手指花总十二现在有什么网站可以做兼职的
  • 做外贸哪几个网站好建网站多少钱建个网站需要怎么做
  • 公司多个门户是做二级域名还是做多个网站wordpress free template
  • 合肥网站建设工作呼伦贝尔网站建设 设计
  • 什么是网络营销品牌网站seo博客
  • 济南新网站优化做网站应该买哪一种服务器
  • 南昌网站搭建公司 赣ICP做网站需要知道哪些事情
  • 淮南家居网站建设怎么样后端网站开发
  • 新闻类的网站有哪些类型开发公司网签补充合同
  • 合肥网站公司哪家好建设银行住房公积金预约网站