最近要公司开发一个机器人分布式调度器,刚刚毕业的我瑟瑟发抖,准备用单例模式向机器人提供调度算法接口,赶紧补习下知识。
什么是单例模式
单例模式是一种常用的设计模式,主要指在程序运行过程中,一个类有且仅有一个实例,并且向外提供获取该实例的接口。实现单例模式有三个要点:类只能有一个实例;类自行创建这个实例;类自行向整个系统提供这个实例。
简单的懒汉式实现
最简单的懒汉式实现单例模式:
class Singleton
{
private:
Singleton() { std::cout << "Constructor!" << std::endl; };
Singleton(Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
//私有化赋值构造函数和拷贝构造函数,避免通过这两种方式有其他实例
static Singleton* instancePtr;
//类的静态局部变量只有一个,作为实例的指针
public:
~Singleton() { std::cout << "Destructor!" << std::endl; };
static Singleton* instance()//静态函数只有一个
{
if(instancePtr == nullptr)
instancePtr = new Singleton;
return instancePtr;
}
void Fun() { std::cout << "Fun" << std::endl; };//其他公有接口
};
存在的问题:
- 线程不安全:如果第一个线程在if中判断为空,同时另一个线程也进入if判断,那么new将会调用两次。解决方法:加锁。
- 内存泄漏:在这个例子中只使用了new但是没有delete对象,这会导致内存泄露。解决方法:智能指针如shared_ptr。
懒汉式改进
考虑到线程安全和内存安全对上例进行改进:
#include <iostream>
#include <memory>
#include <mutex>
class Singleton
{
private:
Singleton() { std::cout << "Constructor!" << std::endl; };
Singleton(Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static std::shared_ptr<Singleton> instancePtr;
static std::mutex sMutex;
//改用共享指针和互斥量
public:
~Singleton() { std::cout << "Destructor!" << std::endl; };
static std::shared_ptr<Singleton> instance()
{
if(instancePtr == nullptr)
{
std::lock_guard<std::mutex> sLock(sMutex);//当实例未创建时需要加锁,lock_guard在构造时加锁,析构时自动释放
if(instancePtr == nullptr)
{
instancePtr = std::make_shared<Singleton>();
}
}
return instancePtr;
}
void Fun() { std::cout << "Fun" << std::endl; };//其他公有接口
};
经典懒汉式magic static
这个写法来自于《Effective C++》系列的作者Meyers,不得不说这个系列的书很经典,我最近每天上班前都要看几个条款。具体写法如下:
#include <iostream>
class Singleton
{
private:
Singleton() { std::cout << "Constructor!" << std::endl; };
Singleton(Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
~Singleton() { std::cout << "Destructor!" << std::endl; };
static Singleton& instance()
{
static Singleton ins;//静态局部变量,内存中只有一个,且只会被初始化一次
return ins;
}
void Fun() { std::cout << "Fun" << std::endl; };//其他公有接口
};
为什么这么简洁的代码能够避免单例模式的各种问题?
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。
简单来说就是当多个线程同时调用instance
接口时,如果ins实例尚未创建,那么就会进入初始化,同时阻塞其他线程初始化该实例,从而保证线程安全,同时由于变量保存在全局静态区,也能够保证内存安全。