七、单例设计模式共享数据分析、解决、call_once
一、设计模式大概谈
代码的一些写法,与常规的写法不太一样,程序灵活,维护起来很方便,但是别人接管、阅读代码很痛苦。
用设计模式理念写出来的代码很晦涩。<< head first>>
老外应付特别大的项目时候,把项目开发经验、模块划分经验,总结成设计模式。
二、单例设计模式
使用频率高。
单例:整个项目中,有某个特殊或某些特殊的类,属于该类的对象,我只能创建1个,多了我就创建不了了。
单例类(构造函数为private);
1 class A{//单例类 2 private: 3 A() {} //私有化构造函数,就不能用A a;这种方式来创建对象了 4 static A* m_instance;//静态成员变量 5 public: 6 static A* GetInstance(){ 7 if(m_instance == NULL){ 8 m_instance = new A(); 9 static huishou cl; 10 11 } 12 return m_instance; 13 } 14 15 class huishou{//类中套类,用来释放对象 16 public: 17 ~huishou(){ 18 if(A::m_instance){//如果这个m_instance不是空,那么就得释放A对象 19 delete A::m_instance; 20 A::m_instance=NULL; 21 } 22 } 23 } 24 25 }; 26 27 //静态变量初始化 28 A* A::m_instance=NULL; 29 30 A *p_a = A::GetInstance();//第一次调用函数,返回m_instance就非空了,当再次创建就不行了。
三、单例设计模式共享数据问题分析、解决
有时候需要在我们自己创建的线程(不是主线程)中创建A这个单例类的对象,这种线程可能最少2个。我们可能会面临
GetInstance()这个成员函数要互斥,例如:
1 using namespace std; 2 std::mutex resource_mutex; 3 4 5 class A{//单例类 6 private: 7 A() {} //私有化构造函数,就不能用A a;这种方式来创建对象了 8 static A* m_instance;//静态成员变量 9 public: 10 static A* GetInstance(){ 11 std::unique_lock<std::mutex> mymutex(resource_mutex);//会自动加锁 12 if(m_instance == NULL){ 13 m_instance = new A(); 14 static huishou cl; 15 16 } 17 return m_instance; 18 } 19 20 class huishou{//类中套类,用来释放对象 21 public: 22 ~huishou(){ 23 if(A::m_instance){//如果这个m_instance不是空,那么就得释放A对象 24 delete A::m_instance; 25 A::m_instance=NULL; 26 } 27 } 28 } 29 30 }; 31 32 //静态变量初始化 33 A* A::m_instance=NULL; 34 35 //线程入口函数 36 void mythread(){ 37 cout << "thread is begining!" <<endl; 38 A* p_a = A::GetInstance();//这里会出问题 39 cout << "this thread over" << endl; 40 return; 41 } 42 43 int main(){ 44 std::thread thread1(mythread); 45 std::thread thread2(mythread); 46 thread1.join(); 47 thread2.join(); 48 return 0; 49 50 }
两个线程的入口函数是相通的,有一种情况可能会发生:当thread1正好创建A对象(正在执行GetInstance)的时候任务被切换到了thread2,那么thread2也会同时执行到GetInstance,相当于两个线程同时执行这个函数,这就坏事了。所以要来一个锁,就行了。
上面的效率太低了,相当于每次创建A都要调用get函数,还要锁上,相当于你这么复杂的步骤,只是为了解决初始化时的问题,这几很低效率了。
怎样高效解决呢?看下面代码:
1 static A* GetInstance(){ 2 //双重锁定提高效率 3 if(m_instance==NULL)//双重锁定,双重检查 4 {
//这段函数也就是只执行一次 5 std::unique_lock<std::mutex> mymutex(resource_mutex);//会自动加锁 6 if(m_instance == NULL){ 7 m_instance = new A(); 8 static huishou cl; 9 } 10 } 11 return m_instance; 12 }
如何提高效率的?
我们知道如果if(m_instance!=NULL)条件成立,表示肯定已m_instance已经被new过了;
如果if(m_instance==NULL),不代表m_instance一定没有被new过,比如上面说的那种特殊情况。
第一个if条件成功的情况就是第一次创建A对象或者几个线程同时第一次创建A对象的手,才会进入第一个if执行语句中,然后就加锁。。。。。。
一旦这个对象已经创建了,那么第一个if里面的执行语句就根本不会再执行了了。也即是说,第一个if就专门是针对出创A对象或多个线程初创A对象的情况,一旦A对象有了,就再也不会执行里面的语句了,效率高了很多。
四、std::call_once()
函数模板
能够保证函数a只被调用一次。
具备互斥量这种能力,效率上比互斥量消耗更少。
需要与一个标记结合使用,这个标记std::once_flag是一个结构。
根据这个标记来决定对应的函数a是否被执行,调用call_once成功后,call_once()就把这个标记设置为一种已调用状态,对应的a函数就不会再被执行了。
1 using namespace std; 2 std::once_flag m_flag;//系统定义的标记 3 4 class A{//单例类 5 static void creatInstance(){//只被调用一次,不加说明都默认成是private 6 m_instance = new A(); 7 static huishou cl; 8 } 9 private: 10 A() {} //私有化构造函数,就不能用A a;这种方式来创建对象了 11 static A* m_instance;//静态成员变量 12 public: 13 static A* GetInstance(){ 14 //如果两个线程同时执行到这里,其中一个线程要等另外一个线程执行完毕creatInstance() 15 std::call_once(m_flag,creatInstance); 16 return m_instance; 17 } 18 19 class huishou{//类中套类,用来释放对象 20 public: 21 ~huishou(){ 22 if(A::m_instance){//如果这个m_instance不是空,那么就得释放A对象 23 delete A::m_instance; 24 A::m_instance=NULL; 25 } 26 } 27 } 28 29 }; 30 31 //静态变量初始化 32 A* A::m_instance=NULL; 33 34 //线程入口函数 35 void mythread(){ 36 cout << "thread is begining!" <<endl; 37 A* p_a = A::GetInstance();//这里会出问题 38 cout << "this thread over" << endl; 39 return; 40 } 41 42 int main(){ 43 std::thread thread1(mythread); 44 std::thread thread2(mythread); 45 thread1.join(); 46 thread2.join(); 47 return 0; 48 49 }
PS:一般建议在主线程中创建单例对象