单例模式的线程安全的实现(五种C++的实现方式和一种C#的实现方式)

一、第一种实现,行为依赖编译器

class Singleton
{
public:
	static Singleton* Get()
	{
		static Singleton s;
		return &s;
	}
private:
	Singleton()
	{
		printf("hello");
	}
};

上述实现方式的线程安全性依赖编译器。如果编译器实现了“静态局部对象初始化的线程安全”那么它是线程安全的,否则不是。visual studio 2015及以后实现了这个特性。关联文档如下:

关于“静态局部对象初始化的线程安全”的文档如下:(1)在visual studio 2015 的更新说明文档中,Thread-Safe "Magic" Statics Static local variables are now initialized in a thread-safe way, eliminating the need for manual synchronization. Only initialization is thread-safe, use of static local variables by multiple threads must still be manually synchronized. The thread-safe statics feature can be disabled by using the /Zc:threadSafeInit- flag to avoid taking a dependency on the CRT. (C++11)。(2)在“ISOIEC 14882 2011”的第6.7.4节的描述为“If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.”

我这里有个测试结果供参考,上述代码在visual studio 2012和visual studio 2015生成的汇编文件不同,如下图:

单例模式的线程安全的实现(五种C++的实现方式和一种C#的实现方式)_第1张图片

二、第二种实现,简单实用,但也有缺点

class Singleton
{
public:
	static Singleton* Get()
	{
		return &s;
	}
private:
	Singleton()
	{
		printf("hello");
	}
private:
	static Singleton s;
};
Singleton Singleton::s;

我经常使用这种实现方法。因为简单又实用。是线程安全的。缺点就是不能控制初始化和销毁的时间。

三、第三种实现,有优点也有缺点

class Singleton
{
public:
	static Singleton* Get()
	{
		lock_guard lock(mut_);
		if (ins_ == nullptr)
		{
			ins_ = new Singleton();
		}
		return ins_;
	}
	static void Destroy()
	{
		lock_guard lock(mut_);
		if (ins_ != nullptr)
		{
			delete ins_;
			ins_ = nullptr;
		}
	}
private:
	Singleton()
	{
		printf("hello\n");
	};
private:
	static Singleton* ins_;
	static mutex mut_;
};
Singleton* Singleton::ins_;
mutex Singleton::mut_;

优点是:能延迟初始化,能主动销毁。显式的进行加锁,明了的表明这是线程安全你的。缺点是:代码显得臃肿。有的人会在乎这个锁,毕竟初始化完成后就不需要再进行多线程的互斥了,但这里每次获取实例都加一次锁。

四、第四种实现,双检锁方式

class Singleton
{
public:
	static Singleton* Get()
	{
		if (ins_ == nullptr)
		{
			lock_guard lock(mut_);
			if (ins_ == nullptr)
			{
				ins_ = new Singleton();
			}
		}
		return ins_;
	}
	static void Destroy()
	{
		lock_guard lock(mut_);
		if (ins_)
		{
			delete ins_;
			ins_ = nullptr;
		}
	}
private:
	Singleton()
	{
		printf("hello\n");
	};
private:
	volatile static atomic ins_;
	static mutex mut_;
};

volatile atomic Singleton::ins_ = nullptr;
mutex Singleton::mut_;

缺点:先说说它的背景:C#.net库使用双检锁实现延迟加载类(即双检锁的单例类);Java SE5之前因为volatile语义没有被JDK开发者正确实现,导致使用早期JDK的时,双检锁的实现不能保证线程安全。再说说它的缺点:因为正确实现双检锁,要求使用者对编译优化、处理器的优化处理有精准的理解,这其实是硬伤,我们不能提倡在刀尖起舞,尤其涉及多线程编程时。包括我的这个示例代码也有些可讨论之处。我使用了volatile 声明ins_为易变的,想禁止编译器把第一个if语句当作冗余读删除,其实这两次读在锁两边,我还是倾向于认为即使不加这个volatile声明,一般编译器也不会激进到做这种优化,但我不敢保证(毕竟原则上冗余读优化与多线程共享变量保护是两回事),这种模棱两可真是可怕

优点:延迟初始化、主动销毁、性能高。

 

五、第五种实现,好不好全凭你的感觉

class Singleton
{
public:
	static Singleton* Get()
	{
		std::call_once(oc_,[]() { ins_ = new Singleton(); });
		return ins_;
	}

private:
	Singleton()
	{
		printf("hello\n");
	};
private:
	static Singleton* ins_;
	static std::once_flag oc_;
};
Singleton* Singleton::ins_ = nullptr;
std::once_flag Singleton::oc_;

用不用这种方法全凭每个人的感觉吧。不算太臃肿,用了一些C++的新特性,也许你不喜欢这些特性。有个问题值得注意网上有人把once_flag的变量声明为Get()的静态局部变量,如果编译器不保证静态局部变量的线程安全性,岂不是多线程不安全了。

六、第六种实现,C#的单例,在C#中就这么写就行

class Singleton
{
    public static Singleton Instance
    {
        get
        {
            return _lazy_ins.Value;
        }
    }
    private Singleton()
    {
        Console.WriteLine("hello");
    }
    private static readonly Lazy _lazy_ins = new Lazy(() => { return new Singleton(); });
}

简单的不要不要的,安全的不要不要的。借助了.net类库中的类Lazy。默认情况下Lazy内部的实现为双检锁模式。可以通过传参指定其他的模式,不过最优的选择还是默认的。

你可能感兴趣的:(C++11)