目录
一、前情回顾
二、volatile关键字
三、加上volatile的改进的线程安全的懒汉式
四、总结
改进的线程安全的懒汉式
class singleton //实现单例模式的类 { private: singleton(){} //私有的构造函数 static singleton* Instance; public: static singleton* GetInstance() { if (Instance == NULL) //第一次判断 { std::lock_guard<std::mutex> guard(mtx); //表示上锁的函数 if (Instance == NULL) //第二次判断 { Instance = new singleton(); } } return Instance; } };
这个方式是线程安全的而且效率高,这个在方式(2)线程安全的懒汉式基础上,又加上了一个判断,不用每次调用锁操作,提高了效率。但是这个方式还是有缺点,就是内存释放问题,谁去释放new出来的内存。
但是如果考虑到编译器优化和编译乱序及执行乱序问题,这个也不是线程安全的。
volatile关键字用来修饰一个变量,提示编译器这个变量的值随时会改变。通常会在多线程、信号处理、中断处理、读取硬件寄存器等场合使用。
程序在执行时,通常将数据(变量的值)从内存的读到寄存器中,然后进行运算,此后对该变量的处理,都是直接访问寄存器就可以了,不再访问内存,因为 访存的代价是很高的(这块是访问寄存器还是重新访存加载到寄存器是编译器在编译阶段就决定了的)。但在上述说的几种情况下,内存会被另一个线程或者信号处 理函数、中断处理函数、硬件改掉,这样,代码只访问寄存器的话,永远得不到真实的值。
对这样的变量(会在多线程、线程与信号、线程与中断处理中共同访问的,或者硬件寄存器),在定义时都会加上volatile关键字修饰。这样编译器 在编译时,编译出的指令会重新访存,这样就能保证拿到正确的数据了。但这里需要注意的是,编译器只能做到让指令重新访问内存,而不是直接使用寄存器中的 值,这些和缓存没有关系,具体执行时指令是访问内存还是访问的缓存,编译器也无法干预。
class singleton //实现单例模式的类 { private: singleton(){} //私有的构造函数 static volatile singleton* Instance; public: static singleton* GetInstance() { if (Instance == NULL) //第一次判断 { std::lock_guard<std::mutex> guard(mtx); //表示上锁的函数 if (Instance == NULL) //第二次判断 { Instance = new singleton(); } } return Instance; } };
加上volatile后,每次访问instance都会重新从内存读取,不会用寄存器的旧值了,因为instance是共享资源,会被不同的线程访问。第二次判断是如果不重新从内存读取,而使用寄存器的旧值,Instance的值有可能已经不是NULL,被其它线程改变了。
对于多线程编程,在不同线程中使用的变量要特别留意,注意线程安全。