我们需要某个共享数据,但它的创建可能需要创建数据库连接,或者分配大量内存,所以等到必要的时候才能连接。这个被称为延迟初始化技术。(lazy initialization)。
#include <mutex>
#include <thread>
#include <vector>
class Data
{
int *data;
public:
Data(){
data=nullptr;
};
~Data(){
if(data!=nullptr)
{
free(data);
data=nullptr;
}
}
void init()
{
data = (int*) malloc(sizeof(int)*1024*1024);//申请大量内存
}
bool isInit()
{
return data!=nullptr;
}
};
class X {
std::mutex resource_mutex;
Data m_data;
void init()
{
std::lock_guard<std::mutex> lk(resource_mutex);
if(m_data.isInit()) //没有被初始化
{
m_data.init();
}
}
};
这样加锁,会出现一个问题,所有的线程都需要循序渐进等锁,然后运行。即使已经初始化完成了,每个线程都要从新初始化一边。
双重检验锁定模式:
class X {
std::mutex resource_mutex;
Data m_data;
void init()
{
if(false==m_data.isInit()) //(1)
{
std::lock_guard<std::mutex> lk(resource_mutex);//(2)
if(false==m_data.isInit()) //没有被初始化//()3
{
m_data.init();//(4)
}
}
}
};
这个存在一定的问题,可能 存在恶性竞争。
如A和B 两个线程同时到达 (1),然后解果都是没有初始化,然后A 到达(2)拿到锁,B在(2)处等待,等A初始化完成后(4)释放了锁,B又从新初始化。造成内存泄漏。
c++ std::call_once() 函数 std::once_flag 类
std::once_flag 的实例,即不可以复制,也不可以移动。
std::call_once()函数只执行一次,因此call_once函数的开销要比加锁省很多。
class X {
std::mutex resource_mutex;
Data m_data;
std::once_flag data_flag;
void init()
{
std::call_once(data_flag, initData);
}
void initData()
{
m_data.init();
}
};
//once_flag的源码。
struct once_flag
{
private:
typedef __gthread_once_t __native_type;
__native_type _M_once = __GTHREAD_ONCE_INIT;
public:
/// Constructor
constexpr once_flag() noexcept = default;
/// Deleted copy constructor
once_flag(const once_flag&) = delete;
/// Deleted assignment operator
once_flag& operator=(const once_flag&) = delete;
template<typename _Callable, typename... _Args>
friend void
call_once(once_flag& __once, _Callable&& __f, _Args&&... __args);
};
静态成员变量单例模式:
c++11 标准发布之后,只允许一个线程对其进行初始化,在其初始化完成之前,其他线程不会越过其声明进行运行。
这使得条件竞争变成由那个线程执行的问题。
如果某些类的代码只使用一个全局的实例,可以用静态局部变量代替call_once()。
class my_class;
my_class& get_instace()
{
static my_class z;
return z;
}