目录
1设计模式
1.1概念
设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
1.2设计模式分类
创建型模式:这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
例如:工厂模式,单例模式
结构型模式:这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
例如:适配器模式,桥接模式
行为型模式:这些设计模式特别关注对象之间的通信。
例如:命令模式,观察者模式
1.3单例模式
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
1.4单例模式代码演示
1.4.1懒汉模式
///懒汉模式
#include<iostream>
#include<pthread.h>
using namespace std;
class Sigleton{
private:
Sigleton(){};
static Sigleton* st;
static pthread_mutex_t g_lock;
public:
static Sigleton* SinFun();
};
pthread_mutex_t Sigleton::g_lock;
Sigleton* Sigleton::st=NULL;
Sigleton* Sigleton::SinFun(){
if(st==NULL){
pthread_mutex_lock(&Sigleton::g_lock);
if(st==NULL){
st=new Sigleton;
}
pthread_mutex_unlock(&Sigleton::g_lock);
}
return st;
}
int main(){
Sigleton* st=Sigleton::SinFun();
Sigleton* st1=Sigleton::SinFun();
if(st==st1){
cout<<"st == st1"<<endl;
}
return 0;
}
1.4.2饿汉模式
#include<iostream>
using namespace std;
class Sigleton{
private:
Sigleton(){
}
static Sigleton* st;
public:
static Sigleton* GetInstance();
void Print(){
cout<<"程序启动就创建对象"<<endl;
}
};
Sigleton* Sigleton::st =new Sigleton;
Sigleton* Sigleton::GetInstance(){
return st;
}
int main(){
Sigleton* st=Sigleton::GetInstance();
st->Print();
return 0;
}
2.读写锁
2.1概念
读写锁是对互斥锁的升级版,适用于大量读少量写的情况。它提供了比互斥锁更好的并行性。因为以读模式加锁后,当有其他线程试图以读模式加锁时,并不会造成这些线程阻塞,但是读写锁更加复杂,开销更大。
读写锁的三种状态:(1)以读模式加锁的状态 (2)以写模式加锁的状态(相当于互斥锁) (3)不加锁的状态
2.2加锁规则
一次只有一个线程可以占用写模式的读写锁,一个执行流在进行写的时候,其他执行流既不能写,也不能读,只能陷入阻塞状态。
多个读取线程可以同时占用读模式下的读写锁,在读写锁的内部有一个引用计数器,这个计数器记录着当前有多少线程以读模式加锁。每当一个线程以读模式打开读写锁的时候,引用计数器+1,当一个线程释放以读模式打开的读写锁的时候,引用计数器-1
作用:判断释放读模式打开的读写锁时,能否完全解锁
如果引用计数器完全减为0,表示当前没有读模式的线程占用读写锁,那么以读模式的读写锁就解锁了,引用计数器大于0,表明还有线程以读模式打开读写锁,就会和想要以写模式打开的读写锁进行互斥。
2.3接口
2.3.1初始化接口
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
参数 pthread_rwlock_t :读写锁类型
rwlock:读写锁对象
attr:属性值
2.3.2销毁接口
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
参数 rwlock:传递的互斥锁对象
2.3.3解锁接口
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
参数 rwlock:传递的互斥锁对象
2.3.4以读模式打开加锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); //阻塞
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); //非阻塞
参数 rwlock:传递的互斥锁对象
2.3.5以写模式加锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); //非阻塞接口
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); //阻塞接口
参数 rwlock:传递的读写锁对象
3.自旋锁
自旋锁(busy-waiting类型)和互斥锁(sleep-waiting类型)的区别:
1.自旋锁加锁时,加不到锁,线程不会切换(时间片没有到的情况,时间片到了,也会线程切换),类似于while循环拿锁,会持续的尝试拿锁,直到拿到自旋锁。
2.互斥锁加锁时,加不到锁,线程会切换(时间片没有到,也会切换),进入睡眠状态,当其他线程释放互斥锁(解锁)之后,被唤醒。在切换回来,进行抢锁。
3.白旋锁的优点:因为白旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。
4.自旋锁的缺点:自旋锁一直占用着CPU,他在未获得锁的情况下,一直运行(自旋),所以占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低
5.适用于临界区代码较短时(直白的说:临界区代码执行时间短)的情况,使用自旋锁效率比较高。因为线程不用来回切换。
6.当临界区当中执行时间较长,自旋锁就不适用了,因为拿不到锁会占用CPU一直抢占锁。
4.乐观锁和悲观锁
悲观锁:在线程访问临界区修改数据时,都会认为有其它线程并行修改情况发生,所以在线程修改数据之前就进行加锁,让多个线程互斥访问。常见的悲观锁有:互斥锁,读写锁,自旋锁等等。
乐观锁:在线程访问临界区修改数据时,乐观的认为只有该线程在修改,大概率不会存在并行的情况。所以修改数据不加锁,但是在修改完毕后,进行判断,例如版本号控制,CAS无所编程。