引言
互斥量是最通用的保护共享数据的机制,用起来不难,而且特别好用。
当然也有其他保护共享数据的机制。
一、设计模式大概谈
“设计模式”:代码的一些写法(这些写法根常规写法不怎么一样):程序灵活,维护起来很方便,但是别人接管、阅读代码都会很痛苦;
用“设计模式”理念写出来的代码是很晦涩的;
老外应付特别大的项目的时候,把项目的开发经验、模块划分经验,总结成设计模式(现有开发需求,后又理论总结和整理)
设计模式拿到中国来,不太一样,拿着一个程序(项目)往设计模式上套,一个小小的项目,非要弄几个设计模式进去,本末倒置。
设计模式肯定有它独特的优点,要活学活用,不要生搬硬套。
二、单例设计模式
单例设计模式,使用的频率比较高;
单例:整个项目中,有某个或者某些特殊的类,属于该类的对象,我只能创建一个,多了我创建不了。
即所谓单例:是整个项目中,有某个或者某些特殊的类,只能创建一个该类的对象。
单例类
class MyCAS
{
private:
MyCAS() {}
private:
static MyCAS* m_instance;
public:
static MyCAS* GetInstance()
{
if (m_instance == NULL)
{
m_instance = new MyCAS();
static CGarhuishou cl;
}
return m_instance;
}
class CGarhuishou //类中套类,用来释放对象
{
public:
~CGarhuishou()
{
if (MyCAS::m_instance)
{
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};
void func()
{
cout << "测试" << endl;
}
};
//类静态变量初始化
MyCAS* MyCAS::m_instance = NULL;
int main()
{
MyCAS* p_a = MyCAS::GetInstance(); //创建一个对象,返回该类(MyCAS)对象的指针
MyCAS* p_b = MyCAS::GetInstance();
//p_a、p_b都指向了同一个对象,所以说MyCAS是一个单例类
p_a->func();
}
三、单例设计模式共享数据问题分析、解决
面临的问题:需要在我们自己创建的线程(而不是主线程)中来创建MyCAS这个单例类的对象,这种线程可能不止一个(最少2个)
我们可能面临GetInstance()这种成员函数要互斥;
void mythread()
{
cout << "我的线程开始执行了" << endl;
MyCAS* p_a = MyCAS::GetInstance();
cout << "我的线程执行完毕了" << endl;
return;
}
int main()
{
std::thread myobj1(mythread);
std::thread myobj2(mythread);
myobj1.join();
myobj2.join();
}
虽然这两个线程是同一个入口函数,但大家千万要记住,这是两个线程,所以这里会有两个流程(两条通路)同时开始执行mythread这个函数
std::mutex resource_mutex;
static MyCAS* GetInstance()
{
std::unique_lock<std::mutex> mymutex(resource_mutex);
if (m_instance == NULL)
{
m_instance = new MyCAS();
static CGarhuishou cl;
}
return m_instance;
}
上面的写法不够高效,更高效的方式:
static MyCAS* GetInstance()
{
//a) 如果if (m_instance != NULL)条件成立,则表示m_instance已经被new过了;
//b) 如果if (m_instance == NULL),不代表m_instance一定没被new过;
//比如说,在没加锁的情况下,线程1刚要执行m_instance = new MyCAS();结果突然切到线程2,线程2new了一把之后切回到线程1,线程1继续new
if (m_instance == NULL) //双重锁定(双重检查)
{
std::unique_lock<std::mutex> mymutex(resource_mutex);
if (m_instance == NULL)
{
m_instance = new MyCAS();
static CGarhuishou cl;
}
}
return m_instance;
}
小结
只有在未初始化时,去判断
if (m_instance == NULL),去加锁
初始化之后的后续都不需要加锁
本节中共享数据m_instance = new MyCAS();
,多个线程同时访问执行的,
通过std::unique_lock
临界上,最外面加上双重锁定来提高多次调用GetInstance的效率
四、std::call_once()
C++11引入的函数,该函数的第二个参数是一个函数名。
call_once
的功能是能够保证函数a()
只被调用一次。
比如,两个线程都调用函数a,只要用call_once
,就能保证a
函数只被调用1次。
call_once
具备互斥量这种能力,而且效率上比互斥量的资源消耗更少。
call_once()
需要与一个标记结合使用,这个标记std::once_flag
。其实once_flag是一个结构,call_once就是通过这个标记决定对应的函数a()是否执行,调用call_once()
成功后,call_once()
就把这个标记设置为一种“已调用”的状态,后续再次调用call_once
,只要once_flag
被设置为了“已调用”状态,那么函数a()
就不会再执行了
例子:
std::once_flag g_flag;
class MyCAS
{
static void CreateInstance()
{
m_instance = new MyCAS();
static CGarhuishou cl;
}
private:
MyCAS() {}
private:
static MyCAS* m_instance;
public:
static MyCAS* GetInstance()
{
std::call_once(g_flag,CreateInstance);//两个线程同时执行到这里,其中一个线程要等另外一个线程执行完毕CreateInstance()
return m_instance;
}
class CGarhuishou //类中套类,用来释放对象
{
public:
~CGarhuishou()
{
if (MyCAS::m_instance)
{
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};
void func()
{
cout << "测试" << endl;
}
};
可以把std::once_flag g_flag
看成一把锁
(感觉还是没听懂)
举个例子:
参考:C++11 std::call_once:保证函数在任何情况下只调用一次
#include <iostream>
#include <thread>
#include <mutex>
std::once_flag flag1;
void simple_do_once()
{
std::call_once(flag1, [](){ std::cout << "Simple example: called once\n"; });
}
int main()
{
std::thread st1(simple_do_once);
std::thread st2(simple_do_once);
std::thread st3(simple_do_once);
std::thread st4(simple_do_once);
st1.join();
st2.join();
st3.join();
st4.join();
}
结果:
Simple example: called once