C++并发与多线程---学习笔记(4)单例设计模式及共享数据分析、解决及实现

C++11并发与多线程

  • 一、单例设计模式讲解及共享数据问题处理、解决
    • (1)单例模式中的懒汉式
    • (2)单例模式中的饿汉式
  • 二、std::call_once()函数模板


一、单例设计模式讲解及共享数据问题处理、解决

  • 什么叫单例设计模式呢,单例设计模式其实就是在整个项目中,有某个或者某些特殊的类,智能创建一个属于该类的对象,单例类就是只能生成一个对象。
  • 单例模式又分为两种类型:懒汉式 与饿汉式,下面就这两种类型展开分析一下

(1)单例模式中的懒汉式

  • 懒汉式的特点是延迟加载,比如配置文件,采用懒汉式的方法去配置文件的实例时,直到用到的时候才会加载,代码如下:
#include

using namespace std;
//懒汉式
class Singleton {
public:
	static Singleton*getInstance() {
		if (instance == nullptr) {
			instance = new Singleton();
		}
		return instance;
	}
private:
	Singleton() {};
	static Singleton*instance;
};
Singleton*Singleton::instance = nullptr;

int main() {

	Singleton*C1 = Singleton::getInstance();
	Singleton*C2 = Singleton::getInstance();

	if (C1 == C2) {
		cout << "一样" << endl;
	}
	else {
		cout << "不一样" << endl;
	}

	system("pause");
	return 0;
}

代码虽然有很高的可读性,但是会存在内存泄漏的问题,new出来的东西始终没有释放,因此我们新建一个类私有化析构它,代码如下:

class Singleton {
public:
	static Singleton*getInstance() {
		if (instance == nullptr) {
			instance = new Singleton();
		}
		return instance;
	}
private:
	Singleton() {};
	static Singleton*instance;
	class Garbo {
	public:
		~Garbo()
		{
			if (Singleton::instance) {
				delete Singleton::instance;
				Singleton::instance = nullptr;
			}
		}
	};
	static Garbo garbo;
};
Singleton*Singleton::instance = nullptr;

这样处理的话就可以保证内存安全一点,但还是可能会泄露并且懒汉式现在还存在一个致命的缺点就是线程不安全的问题,当多个线程都第一次访问该类的是实例的时候有可能会出现创建两个实例,这样就出现内存泄漏了代码如下:

#include
#include
#include
#include

using namespace std;
//懒汉式
class Singleton {
public:
	static Singleton*getInstance() {
		if (instance == nullptr) {
			instance = new Singleton();
		}
		return instance;
	}
private:
	Singleton() {};
	static Singleton*instance;
	class Garbo {
	public:
		~Garbo()
		{
			if (Singleton::instance) {
				delete Singleton::instance;
				Singleton::instance = nullptr;
			}
		}
	};
	static Garbo garbo;	
};
Singleton*Singleton::instance = nullptr;
void test() {
	Singleton*C1 = Singleton::getInstance();
	cout << "创建c1懒汉式单例,thread_id:" << this_thread::get_id() << endl;
}
void test1() {
	Singleton*C2 = Singleton::getInstance();
	cout << "创建c2懒汉式单例,thread_id:" << this_thread::get_id() << endl;
}
int main() {
	thread mythread(test);
	thread mythread1(test1);
	mythread.join();
	mythread1.join();
	system("pause");
	return 0;
}

此时运行该代码就会创建了两次实例,两个线程得到的实例并不是同一个。这是因为两个线程同时运行,当调用getInstance()时,实例并没有创建,于是两个线程都进入了创建实例的代码块,于是就创建了两个实例。而第一个被创建的实例被顶替后也没有被释放,这就是内存泄漏,还有先调用到创建实例的线程还得到了错误的实例,这样会造成逻辑错误的。所有现在有两种方法解决该问题。

  • 第一种:在主函数中先获取一次实例
int main() {
	Singleton::getInstance();
	thread mythread(test);
	thread mythread1(test1);
	mythread.join();
	mythread1.join();
	

	system("pause");
	return 0;
}
  • 第二种:直接加互斥量(锁头),不用先获取一次实例
//懒汉式
mutex myMutex;
class Singleton {
public:
	static Singleton*getInstance() {
		unique_lock<mutex>guard(myMutex);
		if (instance == nullptr) {
			cout << "创建实例" << endl;
			instance = new Singleton();
		}
		return instance;
	}
private:
	Singleton() {};
	static Singleton*instance;
	class Garbo {
	public:
		~Garbo()
		{
			if (Singleton::instance) {
				delete Singleton::instance;
				Singleton::instance = nullptr;
			}
		}
	};
	static Garbo garbo;
	
};

但这种方法还存在一种问题就是当多个线程面临getInstance()这种成员函数时需要互斥出现效率问题,因此为了解决这个问题我们可以再所之前再添加一次判断,这样可以提高效率,核心代码如下:

	static Singleton*getInstance() {
		if (instance == nullptr) {//双重锁定,提高效率
			unique_lock<mutex>guard(myMutex);
			if (instance == nullptr) {
				cout << "创建实例" << endl;
				instance = new Singleton();
			}
		}
		return instance;
	}

(2)单例模式中的饿汉式

  • 饿汉式的特点是一开始就加载了,如果说懒汉式是“时间换空间”,那么饿汉式就是“空间换时间”,因为一开始就创建了实例,所以每次用到的之后直接返回就好了。代码如下:
class Singleton2 {
private:
	Singleton2() {};
	static Singleton2*instance;
public:
	static Singleton2*getInstance() {
		cout << "创建实例" << endl;
		return instance;
	}
};
Singleton2*Singleton2::instance = new Singleton2();
  • 饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变。

二、std::call_once()函数模板

  • std::call_once()函数模板,该函数的第一个参数为标记,第二个参数是一个函数名(如example())。

  • 功能:能够保证函数a()只被调用一次。具备互斥量的能力,而且比互斥量消耗的资源更少,更高效。
    注意事项:

      - call_once()需要与一个标记结合使用,这个标记为std::once_flag;其实once_flag是一个结构,
      - 
      - call_once()就是通过标记来决定函数是否执行,调用成功后,就把标记设置为一种已调用状态。
    
  • 多个线程同时执行时,一个线程就会等待另一个线程先执行。

代码如下:

once_flag g_flag;
class Singelton
{
public:
    static void CreateInstance()//call_once保证其只被调用一次
    {
        instance = new Singelton;
    }
    //两个线程同时执行到这里,其中一个线程要等另外一个线程执行完毕
	static Singelton * getInstance() {
         call_once(g_flag, CreateInstance);
         return instance;
	}
private:
	Singelton() {}
	static Singelton *instance;
};
Singelton * Singelton::instance = NULL;

你可能感兴趣的:(c++,多线程,c++11,c++,设计模式,多线程)