c++单例模式与线程安全(二)
前言上一篇文章中(c++单例模式与线程安全(一)),简单的介绍了单例模式的概念以及两种实现方式(懒汉和饿汉)。这里首先回顾一下懒汉和饿汉的概念。懒汉:在第一次使用时创建实例。饿汉:在类加载时创建实例。线程安全分析试想一下,如果有多线程的情况下,两种单例模式是否安全?是否会出现创建多个实例的情况呢?下面先来看一下饿汉方式的单例模式,在此之前,我再把饿汉方式的实现代码拷贝过来,有助于分析。...
前言
上一篇文章中(c++单例模式与线程安全(一)),简单的介绍了单例模式的概念以及两种实现方式(懒汉和饿汉)。这里首先回顾一下懒汉和饿汉的概念。
懒汉:在第一次使用时创建实例。
饿汉:在类加载时创建实例。
线程安全分析
试想一下,如果有多线程的情况下,两种单例模式是否安全?是否会出现创建多个实例的情况呢?下面先来看一下饿汉方式的单例模式,在此之前,我再把饿汉方式的实现代码拷贝过来,有助于分析。
#include <iostream>
using namespace std;
class Config
{
private:
Config(){cout << "config()" << endl;}
private: static Config* temp;
public:
~Config(){cout << "~Config()" << endl;}
void ReadConfig(){cout << "ReadConfig()" << endl;}
void WriteConfig(){cout << "WriteConfig()" << endl;}
public:
static Config* getInstance()
{
return temp;
}
};
Config* Config::temp = new Config();//创建实例
int main()
{
cout << "main running" << endl;
Config* a = Config::getInstance();
Config* b = Config::getInstance();
return 0;
}
很明显,饿汉是线程安全的。因为饿汉模式下实例很早就创建了,假设在main中创建了两个线程去获取实例,两个线程返回的实例都会是最早创建的那个实例,因此不存在会创建多个实例的情况。
再来看一下懒汉方式下的线程安全,同样的,先拷贝一份懒汉模式的代码以便分析。
#include <iostream>
using namespace std;
class Config
{
private:
Config(){cout << "config()" << endl;}
private: static Config* temp;
public:
~Config(){cout << "~Config()" << endl;}
void ReadConfig(){cout << "ReadConfig()" << endl;}
void WriteConfig(){cout << "WriteConfig()" << endl;}
public:
static Config* getInstance()
{
if(temp == NULL)
temp = new Config();
return temp;
}
};
Config* Config::temp = NULL;
int main()
{
Config* a = Config::getInstance();
Config* b = Config::getInstance();
return 0;
}
我们假设有两个线程需要获取config的实例。当线程1调用getInstance(),并且判断temp == NULL为真,因为此时还没有实例,此时切到线程2也同样调用了getInstance(),并且也判断了temp== NULL为真,然后线程2创建了一个实例,再切回到了线程1 ,这样线程1也创建了实例,可以看出在两个线程下,懒汉有肯能会创建两个实例。
线程安全的单例模式
如何避免这种情况呢?也很简单,加锁就可以了,锁是常用的使线程同步的一种方式。
#include <iostream>
#include <pthread.h>
using namespace std;
class Config
{
private:
static Config* temp;
static pthread_mutex_t mutex;//定义一个锁
Config()
{
cout << "config()" << endl;
pthread_mutex_init(&mutex,NULL);//构造函数中初始化锁,属性为空,表示互斥锁
}
public:
~Config()
{
cout << "~Config()" << endl;
}
void ReadConfig(){cout << "ReadConfig()" << endl;}
void WriteConfig(){cout << "WriteConfig()" << endl;}
public:
static Config* getInstance()
{
pthread_mutex_lock(&mutex);//进入临界区 加锁
if(temp == NULL)
temp = new Config();
pthread_mutex_unlock(&mutex);//出临界区 解锁
return temp;
}
};
Config* Config::temp = NULL;
pthread_mutex_t Config::mutex;
int main()
{
cout << "main running" << endl;
Config* a = Config::getInstance();
Config* b = Config::getInstance();
return 0;
}
互斥锁是比较常用的一种锁,在访问全局变量时之前加锁,访问完之后解锁,这样可以保证多线程中的全局变量是同步的。采用上述加锁的代码之后,我们再来分析一下两个线程的情况。
首先线程1调用getInstance()函数并且对mutex加锁,判断temp == NULL为真,进入条件语句中,此时切到线程2,也调用getInstance()函数,由于mutex已经被线程1锁定,线程2不持有mutex,所以线程2会阻塞pthread_mutex_lock这个函数中,然后又切回到线程1,线程1创建了一个单例的实例,并且解锁,再切回到线程2,线程2给mutex加锁,再判断temp == NULL,这个条件为假,因为temp的值已经被线程1给赋值了,所以直接返回了temp,即线程1创建的实例,再解锁。这样就满足了在多线程的情况下也不会创建多个实例,满足单例模式的设计思想。
更多推荐
所有评论(0)