详解如何使用C++写一个线程安全的单例模式_第1页
详解如何使用C++写一个线程安全的单例模式_第2页
详解如何使用C++写一个线程安全的单例模式_第3页
详解如何使用C++写一个线程安全的单例模式_第4页
详解如何使用C++写一个线程安全的单例模式_第5页
已阅读5页,还剩2页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第详解如何使用C++写一个线程安全的单例模式目录单例模式的简单实现有问题的双重检测锁现代C++中的解决方法使用现代C++中的内存顺序限制使用现代C++中的call_once方法使用静态局部变量

单例模式的简单实现

单例模式大概是流传最为广泛的设计模式之一了。一份简单的实现代码大概是下面这个样子的:

classsingleton

public:

staticsingleton*instance()

if(inst_!=nullptr){

inst_=newsingleton();

returninst_;

private:

singleton(){}

staticsingleton*inst_;

singleton*singleton::inst_=nullptr;

这份代码在单线程的环境下是完全没有问题的,但到了多线程的世界里,情况就有一点不同了。考虑以下执行顺序:

线程1执行完if(inst_!=nullptr)之后,挂起了;线程2执行instance函数:由于inst_还未被赋值,程序会inst_=newsingleton()语句;线程1恢复,inst_=newsingleton()语句再次被执行,单例句柄被多次创建。

所以,这样的实现是线程不安全的。

有问题的双重检测锁

解决多线程的问题,最常用的方法就是加锁呗。于是很容易就可以得到以下的实现版本:

classsingleton

public:

staticsingleton*instance()

guardmutexlock{mut_};

if(inst_!=nullptr){

inst_=newsingleton();

returninst_;

private:

singleton(){}

staticsingleton*inst_;

staticmutexmut_;

singleton*singleton::inst_=nullptr;

mutexsingleton::mut_;

这样问题是解决了,但性能上就不那么另人满意,毕竟每一次使用instance都多了一次加锁和解锁的开销。更关键的是,这个锁也不是每次都需要啊!实际我们只有在创建单例实例的时候才需要加锁,之后使用的时候是完全不需要锁的。于是,有人提出了一种双重检测锁的写法:

...

staticsingleton*instance()

if(inst_!=nullptr){

guardmutexlock{mut_};

if(inst_!=nullptr){

inst_=newsingleton();

returninst_;

我们先判断一下inst_是否已经初始化了,如果没有,再进行加锁初始化流程。这样,虽然代码看上去有点怪异,但好像确实达到了只在创建单例时才引入锁开销的目的。不过遗憾的是,这个方法是有问题的。ScottMeyers和AndreiAlexandrescu两位大神在C++andthePerilsofDouble-CheckedLocking一文中对这个问题进行了非常详细地讨论,我们在这儿只作一个简单的说明,问题出在:

inst_=newsingleton();

这一行。这句代码不是原子的,它通常分为以下三步:

调用operatornew为singleton对象分配内存空间;在分配好的内存空间上调用singleton的构造函数;将分配的内存空间地址赋值给inst_。

如果程序能严格按照1--2--3的步骤执行代码,那么上述方法没有问题,但实际情况并非如此。编译器对指令的优化重排、CPU指令的乱序执行(具体示例可参考《【多线程那些事儿】多线程的执行顺序如你预期吗?》)都有可能使步骤3执行早于步骤2。考虑以下的执行顺序:

线程1按步骤1--3--2的顺序执行,且在执行完步骤1,3之后被挂起了;线程2执行instance函数获取单例句柄,进行进一步操作。

由于inst_在线程1中已经被赋值,所以在线程2中可以获取到一个非空的inst_实例,并继续进行操作。但实际上单例对像的创建还没有完成,此时进行任何的操作都是未定义的。

现代C++中的解决方法

在现代C++中,我们可以通过以下几种方法来实现一个即线程安全、又高效的单例模式。

使用现代C++中的内存顺序限制

现代C++规定了6种内存执行顺序。合理的利用内存顺序限制,即可避免代码指令重排。一个可行的实现如下:

classsingleton{

public:

staticsingleton*instance()

singleton*ptr=inst_.load(memory_order_acquire);

if(ptr==nullptr){

lock_guardmutexlock{mut_};

ptr=inst_.load(memory_order_relaxed);

if(ptr==nullptr){

ptr=newsingleton();

inst_.store(ptr,memory_order_release);

returninst_;

private:

singleton(){};

staticmutexmut_;

staticatomicsingleton*inst_;

mutexsingleton::mut_;

atomicsingleton*singleton::inst_;

来看一下汇编代码:

可以看到,编译器帮我们插入了必要的语句来保证指令的执行顺序。

使用现代C++中的call_once方法

call_once也是现代C++中引入的新特性,它可以保证某个函数只被执行一次。使用call_once的代码实现如下:

classsingleton

public:

staticsingleton*instance()

if(inst_!=nullptr){

call_once(flag_,create_instance);

returninst_;

private:

singleton(){}

staticvoidcreate_instance()

inst_=newsingleton();

staticsingleton*inst_;

staticonce_flagflag_;

singleton*singleton::inst_=nullptr;

once_flagsingleton::flag_;

来看一下汇编代码:

可以看到,程序最终调用了__gthrw_pthread_once来保证函数只被执行一次。

使用静态局部变量

现在C++对变量的初始化顺序有如下规定:

Ifcontrolentersthedeclarationconcurrentlywhilethevariableisbeinginitialized,theconcurrentexecutionshallwaitforcompletionoftheinitialization.

所以我们可以简单的使用一个静态局部变量来实现线程安全的单例模式:

classsingleton

public:

staticsingleton*instance()

staticsingletoninst_

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论