1.饿汉模式
class TaskQueue
{
public:
// = delete 代表函数禁用, 也可以将其访问权限设置为私有
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
static TaskQueue* getInstance()
{
return m_taskQ;
}
private:
TaskQueue() = default;
static TaskQueue* m_taskQ;
};
//静态成员初始化放到类外部处理
TaskQueue* TaskQueue::m_taskQ = new TaskQueue;
int main()
{
TaskQueue* obj = TaskQueue::getInstance();
}
2.懒汉模式
2.1一把互斥锁实现
//加了一把互斥锁,解决多线程问题,但是性能不好
#include <iostream>
#include <queue>
#include <mutex>
#include <thread>
class TaskQueue
{
public:
// = delete 代表函数禁用, 也可以将其访问权限设置为私有
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
static TaskQueue* getInstance()
{
m_mutex.lock();
if (m_taskQ == nullptr)
{
m_taskQ = new TaskQueue;
}
m_mutex.unlock();
return m_taskQ;
}
private:
TaskQueue() = default;
static TaskQueue* m_taskQ;
static std::mutex m_mutex;
};
TaskQueue* TaskQueue::m_taskQ = nullptr;
std::mutex TaskQueue::m_mutex;
int main()
{
TaskQueue* obj = TaskQueue::getInstance();
}
2.2 静态局部对象实现
//静态局部对象:在实现懒汉模式的单例的时候,相较于双重检查锁定模式有一种更简单的实现方法并且不会出现线程安全问题,那就是使用静态局部局部对象
#include <iostream>
#include <queue>
using namespace std;
class TaskQueue
{
public:
// = delete 代表函数禁用, 也可以将其访问权限设置为私有
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
static TaskQueue* getInstance()
{
static TaskQueue taskQ;
return &taskQ;
}
void print()
{
cout << "hello, world!!!" << endl;
}
private:
TaskQueue() = default;
};
int main()
{
TaskQueue* queue = TaskQueue::getInstance();
queue->print();
return 0;
}
2.3 原子变量实现
#include <iostream>
#include <queue>
#include <mutex>
#include <thread>
#include <atomic>
using namespace std;
class TaskQueue
{
public:
// = delete 代表函数禁用, 也可以将其访问权限设置为私有
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
static TaskQueue* getInstance()
{
TaskQueue* queue = m_taskQ.load();
if (queue == nullptr)
{
// m_mutex.lock(); // 加锁: 方式1
lock_guard<mutex> locker(m_mutex); // 加锁: 方式2
queue = m_taskQ.load();
if (queue == nullptr)
{
queue = new TaskQueue;
m_taskQ.store(queue);
}
// m_mutex.unlock();
}
return queue;
}
void print()
{
cout << "hello, world!!!" << endl;
}
private:
TaskQueue() = default;
static atomic<TaskQueue*> m_taskQ;
static mutex m_mutex;
};
atomic<TaskQueue*> TaskQueue::m_taskQ;
mutex TaskQueue::m_mutex;
int main()
{
TaskQueue* queue = TaskQueue::getInstance();
queue->print();
return 0;
}
3.分析
3.1 更推荐懒汉模式
懒汉模式的缺点是在创建实例对象的时候有安全问题,但这样可以减少内存的浪费(如果用不到就不去申请内存了)。
饿汉模式则相反,在我们不需要这个实例对象的时候,它已经被创建出来,占用了一块内存。对于现在的计算机而言,内存容量都是足够大的,这个缺陷可以被无视。
4.单例模式的模板和继承
4.1 Meyer’s Singleton
Scott Meyers 在 Effective C++ 的 Item 4: Make sure that objects are initialized before they’re used 里面提出了一种利用 C++ 的 static
关键字来实现的单例模式,这种实现非常简洁高效,它的特点是:
- 仅当程序第一次执行到
GetInstance
函数时,执行instance
对象的初始化; - 在 C++ 11 之后,被
static
修饰的变量可以保证是线程安全的;(即:如果指令逻辑进入一个未被初始化的声明变量,所有并发执行应当等待该变量完成初始化。)
template<typename T>
class Singleton
{
public:
static T& GetInstance()
{
static T instance;
return instance;
}
Singleton(T&&) = delete;
Singleton(const T&) = delete;
void operator= (const T&) = delete;
protected:
Singleton() = default;
virtual ~Singleton() = default;
};
通过禁用单例类的 copy constructor,move constructor 和 operator= 可以防止类的唯一实例被拷贝或移动;不暴露单例类的 constructor 和 destructor 可以保证单例类不会通过其他途径被实例化,同时将两者定义为 protected 可以让其被子类继承并使用。
4.2 Eager Singleton
和 Lazy Singleton 相反,Eager Singleton 利用 static member variable 的特性,在程序进入 main 函数之前进行初始化,这样就绕开了线程安全的问题:
template<typename T>
class EagerSingleton
{
private:
static T* t_;
public:
static T& GetInstance()
{
return *t_;
}
EagerSingleton(T&&) = delete;
EagerSingleton(const T&) = delete;
void operator= (const T&) = delete;
protected:
EagerSingleton() = default;
virtual ~EagerSingleton() = default;
};
template<typename T>
T* EagerSingleton<T>::t_ = new (std::nothrow) T;
但是它也有两个问题:
- 即使单例对象不被使用,单例类对象也会进行初始化;
- static initialization order fiasco,即 t_ 对象和
GetInstance
函数的初始化先后顺序是不固定的;
4.3 Lazy Singleton
Lazy Singleton 是一种比较传统的实现方法,通过其名字可以看出来它也具有 lazy-evaluation 的特点,但在实现的时候需要考虑线程安全的问题:
template<typename T, bool is_thread_safe = true>
class LazySingleton
{
private:
static unique_ptr<T> t_;
static mutex mtx_;
public:
static T& GetInstance()
{
if (is_thread_safe == false)
{
if (t_ == nullptr)
t_ = unique_ptr<T>(new T);
return *t_;
}
if (t_ == nullptr)
{
unique_lock<mutex> unique_locker(mtx_);
if (t_ == nullptr)
t_ = unique_ptr<T>(new T);
return *t_;
}
}
LazySingleton(T&&) = delete;
LazySingleton(const T&) = delete;
void operator= (const T&) = delete;
protected:
LazySingleton() = default;
virtual ~LazySingleton() = default;
};
template<typename T, bool is_thread_safe>
unique_ptr<T> LazySingleton<T, is_thread_safe>::t_;
template<typename T, bool is_thread_safe>
mutex LazySingleton<T, is_thread_safe>::mtx_;
我们通过模板参数 is_thread_safe
来控制这个类是否是线程安全的,因为在某些场景下我们会希望每个线程拥有一个实例:
- 当
is_thread_safe == false
,即非线程安全时,我们在GetInstance
函数中直接判断,初始化并返回单例对象;这里使用了unique_ptr
防止线程销毁时发生内存泄漏,也可以在析构函数中销毁指针; - 当
is_thread_safe == true
时,我们通过 double-checked locking 来进行检查并加锁,防止单例类在每个线程上都被实例化。
5.TaskQueue实战
#include <functional>
#include <memory>
#include<string>
#include <iostream>
#include <queue>
#include <mutex>
#include <thread>
//单例模板类
template<typename T>
class EagerSingleton
{
private:
static T* t_;
public:
static T* GetInstance()
{
return t_;
}
EagerSingleton(T&&) = delete;
EagerSingleton(const T&) = delete;
void operator= (const T&) = delete;
protected:
EagerSingleton() = default;
virtual ~EagerSingleton() = default;
};
// 任务队列的类,继承自单例模板类
template<typename T>
T* EagerSingleton<T>::t_ = new (std::nothrow) T;
class TaskQueue :public EagerSingleton<TaskQueue>
{
public:
// 任务队列是否为空
bool isEmpty()
{
std::lock_guard<std::mutex> locker(m_mutex);
bool flag = m_taskQ.empty();
return flag;
}
// 添加任务
void addTask(std::function<void()> task)
{
std::lock_guard<std::mutex> locker(m_mutex);
m_taskQ.push(task);
}
// 从队列中取出一个任务
std::function<void()> getTask()
{
std::lock_guard<std::mutex> locker(m_mutex); // 上锁
if (m_taskQ.empty()) { // 队列为空,返回空函数
return [] {};
}
auto task = m_taskQ.front(); // 取出队首元素
m_taskQ.pop(); // 出队
return task; // 返回任务函数
}
// 删除一个任务
bool popTask()
{
std::lock_guard<std::mutex> locker(m_mutex);
if (!m_taskQ.empty())
{
m_taskQ.pop();
return true;
}
return false;
}
private:
std::queue<std::function<void()>> m_taskQ;
std::mutex m_mutex;
};
// WorkItem 类的定义,是一个抽象的基类
class WorkItem {
public:
// 构造函数,接受一个任务名
WorkItem(const std::string& name) : name_(name) {}
// 虚析构函数,用于支持多态
virtual ~WorkItem() {}
// 纯虚函数,用于执行任务的具体逻辑,由子类实现
virtual void run() = 0;
// 获取任务名
std::string getName() const {
return name_;
}
private:
std::string name_; // 任务名
};
// PrintTask 类的定义,继承自 WorkItem 类
class PrintTask : public WorkItem {
public:
// 构造函数,调用基类的构造函数
PrintTask(const std::string& name) : WorkItem(name) {}
// 重写 run 方法,打印任务名
void run() override {
std::cout << "###Running task: " << getName() << "### ";
}
};
int main()
{
// 创建三个任务对象
std::thread t1([]() {
TaskQueue* taskQ = TaskQueue::GetInstance();
for (int i = 0; i < 100; ++i)
{
auto task = std::make_shared<PrintTask>("task" + std::to_string(i + 100));
taskQ->addTask(std::bind(&PrintTask::run, task));
std::cout << "+++push task: " << i + 100 << ", threadID: "
<< std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
});
std::thread t2([]() {
TaskQueue* taskQ = TaskQueue::GetInstance();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
while (!taskQ->isEmpty())
{
std::cout << "---take task: "; taskQ->getTask()();
std::cout << " threadID: "
<< std::this_thread::get_id() << std::endl;
taskQ->getTask()();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
std::thread t3([]() {
TaskQueue* taskQ = TaskQueue::GetInstance();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
while (!taskQ->isEmpty())
{
std::cout << "---take task: "; taskQ->getTask()();
std::cout << " threadID: "
<< std::this_thread::get_id() << std::endl;
taskQ->getTask()();
std::this_thread::sleep_for(std::chrono::seconds(1));
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
t1.join();
t2.join();
t3.join();
}