目录
线程池
- 1.应用场景
- 1.1 一个线程在被创建之后,只能执行一个线程入口函数,后续是没有办法更改的。基于这种场景,线程可能执行的代码也就是固定了。换句话说线程入口函数当中有很多分支语句,对于不同的线程执行的路线都是固定的,只是分支不同罢了,要么是A分支,要么是B分支,要么是C分支...
- 1.2所以为了能给让线程执行不一样的业务代码,就要考虑线程从队列当中获取的元素身上下功夫(该元素包含两部分:待要处理的数据和处理该数据的方法)。让线程可以通过线程元素来执行不一样的代码。
- 2.线程池要完成的事情
- 2.1创建固定数量线移池,循环从任务队列中获取任务对象
- 2.2获取到任务对象后,执行任务对象中的任务接口
- 代码模拟:
-
#include<iostream> using namespace std; #include<stdio.h> #include<queue> #include<pthread.h> #include<unistd.h> typedef void (*Hander)(int data); class QueueDate{ public: QueueDate(){ } QueueDate(int data,Hander hander){ _data=data; _hander=hander; } void run(){ _hander(_data); } private: int _data; Hander _hander; }; class ThreadPool{ private: ThreadPool(int capacity,int thread_count){ _capacity=capacity; pthread_mutex_init(&_lock,NULL); pthread_cond_init(&_cons_cond,NULL); pthread_cond_init(&_prod_cond,NULL); _thread_count=thread_count; _flag_exit=0; } public: ~ThreadPool(){ pthread_mutex_destroy(&_lock); pthread_cond_destroy(&_cons_cond); pthread_cond_destroy(&_prod_cond); } int OnInit(){ int cnt=0; for(int i=0;i<_thread_count;i++){ pthread_t tid; int ret=pthread_create(&tid,NULL,thread_pool_start,(void*)this); if(ret<0){ cnt++; } } return _thread_count-=cnt; } void Pop(QueueDate* qd){ *qd=_que.front(); _que.pop(); } void Push(QueueDate qd){ pthread_mutex_lock(&_lock); while(_que.size() >= _capacity){ if(_flag_exit){ pthread_mutex_unlock(&_lock); return; } pthread_cond_wait(&_prod_cond,&_lock); } _que.push(qd); pthread_mutex_unlock(&_lock); pthread_cond_signal(&_cons_cond); } static void* thread_pool_start(void* arg){ pthread_detach(pthread_self()); ThreadPool* tp=(ThreadPool*)arg; while(1){ pthread_mutex_lock(&tp->_lock); while(tp->_que.empty()){ if(tp->_flag_exit){ tp->_thread_count--; pthread_mutex_unlock(&tp->_lock); pthread_exit(NULL); } pthread_cond_wait(&tp->_cons_cond,&tp->_lock); } QueueDate qd; tp->Pop(&qd); pthread_mutex_unlock(&tp->_lock); pthread_cond_signal(&tp->_prod_cond); qd.run(); } } void ThreadPoolExit(){ _flag_exit=1; while(_thread_count > 0){ pthread_cond_signal(&_cons_cond); } } private: queue<QueueDate> _que; size_t _capacity; pthread_mutex_t _lock; pthread_cond_t _cons_cond; pthread_cond_t _prod_cond; int _thread_count; int _flag_exit; private: static ThreadPool* tp; public: static ThreadPool* getInstance(); }; ThreadPool*ThreadPool::tp=new ThreadPool(10,5); ThreadPool*ThreadPool::getInstance(){ return tp; } void DealDate(int data){ printf("data=%d\n",data); } int main(){ ThreadPool* tp=ThreadPool::getInstance(); if(tp == NULL){ printf("create thread pool failed\n"); return 0; } int ret=tp->OnInit(); if(ret <= 0){ printf("create thread failed\n"); return 0; } for(int i=0;i<10000;i++){ QueueDate qd(i,DealDate); tp->Push(qd); } tp->ThreadPoolExit(); delete tp; return 0; }
读写锁
- 大量读,少量写的情况。
- 允许多个线程并行读,多个线程互斥写。
- 读写锁的三种状态:
- 以读模式加锁的状态
- 以写模式加锁的状态(互斥锁)
- 不加锁的状态
- 初始化接口
- int pthread_rwlock_init(pthread_rwlock_t * rwlock, const pthread_rwlockattr_t *attr);
- pthread_rwlock_t :读写锁的类型
- rwlock :传递读写锁
- attr : NULL,默认属性
- int pthread_rwlock_init(pthread_rwlock_t * rwlock, const pthread_rwlockattr_t *attr);
- 销毁接口
- int pthread_rwlock_destroy (pthread_rwlock_t *rwlock) ;
- 加锁接口:
- 以读模式进行加锁
- int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);(阻塞接口)
- 以读模式进行加锁,允许多个线程并行以读模式获取读写锁。
- 引用计数:用来记录当前读写锁有多少个线程以读模式获取了读写锁。
- 1.每当有线程以读模式进行加锁,引用计数++;
- 2.每当读模式的线程释放锁,引用计数--;
- int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); (非阻塞接口)
- int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);(阻塞接口)
- 以写模式进行加锁
- int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);(阻塞接口)
- 相当于互斥锁
- int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);(阻塞接口)
- 以读模式进行加锁
- 解锁接口:
- int pthread_rwlock_unlock(pthread_rwlock_t *rwlock) ;
- 读写锁内部的规则:以写模式加锁的线程想要去获取读写锁的时候,当前读写锁还在被以读模式加锁的线程所持有,则后续想要以读模式加锁的线程就会被阻塞
单例模式
- 1.设计模式
- 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过当长的一段时间的试验和错误总结出来的
- 2.设计模式分类: https: //www.runoob.com/design-pattern/design-pattern-intro.html
- 创建型模式。这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用new运算符直接实例化对象这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
- 工厂模式(Factory Pattern)
- 单例模式(Singleton Pattern)
- 结构型模式:这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
- 适配器模式(Adapter Pattern)
- 桥接模式(Bridge Pattern)
- 行为型模式:这些设计模式特别关注对象之间的通信。
- 命令模式(Command Pattern)(
- 观察者模式(0bserver Pattern)
- 创建型模式。这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用new运算符直接实例化对象这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
- 3.单例模式:
- 1、单例类只能有一个实例。(在整个软件当中就只有一个实例对象)
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
- 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- 主要解决:一个全局使用的类频繁地创建与销毁。
- 何时使用:当您想控制实例数目,节省系统资源的时候。
- 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
- 关键代码:构造函数是私有的。
- 4.单例模式两种形式(懒汉& 饿汉)
- 饿汉模式:在程序启动的时候就创建唯一的实例对象,饿汉模式不需要加锁
- 示例代码:
- 懒汉模式:当你第一次使用时才创建一个唯一的实例对象,从而实现延迟加载效果。
- 示例代码;
- 注意:线程安全:
- 双层if判断,加锁的位置
- 懒汉模式在第一次使用单例对象时才完成初始化工作,因此可能存在多线程竞态环境,如果不加锁会导致重复构造或构造不完全
- 饿汉模式:在程序启动的时候就创建唯一的实例对象,饿汉模式不需要加锁
乐观锁&悲观锁
- 悲观锁:
- 针对某个线程访问临界区修改数据的时候,都会认为可能有其他线程并行修改的情况发生,所以在线程修改数据之前就进行加锁,让多个线程互斥访问。悲观锁有:互斥锁,读写锁,自旋锁等等
- 乐观锁:
- 针对某个线程访问临界区修改数据的时候,乐观的认为只有该钱程在修改,大概率不会存在并行的情况。所以修改数据不加锁,但是,在修改完毕,进行更新的时候,进行判断。例如:版本号控制,CAS无锁编程
- 自旋锁
- 头文件 <pthread.h>
- 自旋锁接口
- int pthread_spin_init (pthread_spinlock_t *lock,int pshared);
- pthread_spinlock_t :自旋锁的类型
- int pthread_spin_destroy(pthread_spinlock_t *lock);
- int pthread_spin_lock(pthread_spinlock_t *lock);
- int pthread_spin_trylock(pthread_spinlock_t *lock);
- int pthread_spin_unlock(pthread_spinlock_t *lock);
- int pthread_spin_init (pthread_spinlock_t *lock,int pshared);
- 自旋锁(busy-waiting类型)和互斥锁(sleep-waiting类型)的区别:
- 1.自旋锁加锁时,加不到锁,线程不会切换(时间片没有到的情况,如果时间片到了,也会线程切换),会持续的尝诗拿锁,直到拿到自旋锁,忙等类型的锁
- 2.互斥锁加锁时,加不到锁,线程会切换(时间片没有到,也会切换),进入睡眼状态,当其他线程释放互斥锁(解锁)之后,被唤醒。在切换回来,进行抢锁。睡等类型的锁
- 3.自旋锁的优点:因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。
- 4.自旋锁的缺点:自旋锁一直占用着CPU,他在未获得锁的情况下,一直运行自旋,所以占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低。
- 5.自旋锁适用于临界区代码较短时(直白的说:临界区代码执行时间短〉的情况,使用自旋锁效率比较高。因为线程不用来回切换。
- 6.当临界区当中执行时间较长,自旋锁就不适用了,因为拿不到锁会占用CPU一直自旋抢锁。
- 版本号控制:
- 检查版本号,如果修改之前的版本号和修改之后的版本号一致,允许修改,修该完毕,产生新的版本号。
- 无锁编程(CAS : Compare And Swap)
- CAS机制当中三个操作数:
- V内存地址
- A 旧的预期值
- B要将内存地址值修改成的新值
- 在修改V对应的内存内容的时候,先进行比较V的值和A的值是否相等
- 如果相等:说明没有人修改过,则将B的值赋值给V
- 如果不相等:说明有人修改过,则重新获取V的值,重新进行判断,直到V的值和A的值相等
- 无锁编程中的ABA 问题
- 某个线程在比较V的值和A的值的时候,存在一种情况,内存中的值已经发生变化,但是变化后又回到了最开始的值(例如:先加1,再减1),虽然对线程修改内存中的值不影响,但是线程并不知道内存中的值已经发生了变化。解决方案:版本号控制。
- CAS机制当中三个操作数: