利用C++11特性实现的多线程安全的单例模式

单例模式保证某个类对象在进程中只被初始化一次,使用的场景是对一个可复用的资源在一个进程中只初始化一次直到进程结束再释放,这样就可以避免重复构造析构带来的额外性能消耗

 

目录

饿汉模式

 懒汉模式

 简单加锁实现的低效的线程安全

 臭名昭著的二次锁定检查模式(实际上非线程安全)

 利用C++11的call_once实现线程安全的单例模式

参考书籍


饿汉模式

 饿汉模式的特点是空间换时间,无论我们需要的类在什么时候使用,一律在进程开始时进行初始化,优点当然是方便不会有什么线程安全问题,缺点是会带来额外的空间损耗

#include
class Singleton
{
private:
	Singleton()
	{
		std::cout << "Singleton()" << std::endl;
	}
	static Singleton* p;
public:
	static Singleton* instance();

	void show()
	{
		std::cout << "Show Singleton" << std::endl;
	}
};

Singleton* Singleton::p = new Singleton();

Singleton* Singleton::instance()
{
	return p;
}


int main()
{
	Singleton* temp = Singleton::instance();

	temp->show();

	system("pause");
	return 0;
}

 懒汉模式

 懒汉模式的特点是时间换空间,只有在类对象真正被使用到时才进行初始化

 简单加锁实现的低效的线程安全

#include
#include
class Singleton
{
private:
	Singleton()
	{
		std::cout << "Singleton()" << std::endl;
	}
	static Singleton* p;
	
public:
	static Singleton* instance();
	static std::mutex mutex_;
	void show()
	{
		std::cout << "Show Singleton" << std::endl;
	}
};
std::mutex Singleton::mutex_;
Singleton* Singleton::p = nullptr;

Singleton* Singleton::instance()
{
	{
		std::lock_guard lock(mutex_);
		if (!p)
		{
			p = new Singleton;
		}
	}
	

	return p;
}

int main()
{
	Singleton* temp = Singleton::instance();
	temp->show();

	system("pause");
	return 0;
}

 缺点是每次访问前都要加锁,造成不必要的性能浪费

 臭名昭著的二次锁定检查模式(实际上非线程安全)

 为了解决之前的问题,就想出了二次锁定模式,这个模式的思路是既然每次获取实例时都要加锁太麻烦了,那么我只要在获取锁之前先判断实例是否已经生成,如果是实例还未生成就获取锁来生成实例,否则就直接返回实例

#include
#include
class Singleton
{
private:
	Singleton()
	{
		std::cout << "Singleton()" << std::endl;
	}
	static Singleton* p;

public:
	static Singleton* instance();
	static std::mutex mutex_;
	void show()
	{
		std::cout << "Show Singleton" << std::endl;
	}
};
std::mutex Singleton::mutex_;
Singleton* Singleton::p = nullptr;

Singleton* Singleton::instance()
{
	if (!p) // 1
	{
		std::lock_guard lock(mutex_);
		if (!p)
		{
			p = new Singleton;  //2
		}		
	}

	return p;
}

int main()
{
	Singleton* temp = Singleton::instance();
	temp->show();

	system("pause");
	return 0;
}

这个方法看似很美好,但实际上却有一个非常致命的错误,那就是注释2中的new 这一过程本身并非原子操作,我们都知道new表达式实际上有两个步骤组成,一是调用operator new来申请足够的堆空间,二是在申请的堆空间上调用构造函数来构造对象。问题就在于注释2中的过程无法保证是在对象构建完成后将地址赋值给对象p,还是向将地址赋值给对象p后再构造对象,如果是后者的话那么在其他进程进行到步骤1 是就可能产生错误的判断导致在未构建的对象上进行操作。 

 利用C++11的call_once实现线程安全的单例模式

 

#include
#include
#include
class Singleton
{
private:
	Singleton()
	{
		std::cout << "Singleton()" << std::endl;
	}
	static Singleton* p;

public:
	static Singleton* instance();
	static std::once_flag init_flag;
	void show()
	{
		std::cout << "Show Singleton" << std::endl;
	}
};

std::once_flag Singleton::init_flag;
Singleton* Singleton::p = nullptr;

Singleton* Singleton::instance()
{
	std::call_once(init_flag, [] {p = new Singleton; });

	return p;
}

int main()
{
	Singleton* temp = Singleton::instance();
	temp->show();

	system("pause");
	return 0;
}

Std::call_once保证了与once_flag关联的操作只会被调用一次

参考书籍

C++并发编程实战

你可能感兴趣的:(C++相关,单例模式,c++)