使用C++无锁编程实现多线程下的单例模式
贺志国
2023.8.1
一、尺寸较小的类单例对象创建
如果待创建的单例类SingletonForMultithread
内包含的成员变量较少,整个类占用的内存空间较小,则可以使用如下方法来创建单例对象(如果类的尺寸较大则不允许,64位Linux系统默认栈的最大空间为8 MB,64位Windows系统默认栈的最大空间为1 MB):
class SmallSingletonForMultithread {
public:
static SmallSingletonForMultithread& GetInstance() {
static SmallSingletonForMultithread instance;
return instance;
}
private:
SmallSingletonForMultithread() = default;
~SmallSingletonForMultithread() = default;
SmallSingletonForMultithread(const SmallSingletonForMultithread&) = delete;
SmallSingletonForMultithread& operator=(const SmallSingletonForMultithread&) = delete;
SmallSingletonForMultithread(SmallSingletonForMultithread&&) = delete;
SmallSingletonForMultithread& operator=(SmallSingletonForMultithread&&) = delete;
};
二、尺寸较大的类单例对象创建(要求显式调用销毁函数来避免内存泄漏)
在实际工作中,由于某些单例类的尺寸较大,静态变量存储栈区无法容纳该单例对象,因此无法使用上述方法来创建单例对象,这时需要使用new
在堆区动态创建单例对象。为了避免多线程环境下对于单例对象的抢夺,可使用C++无锁编程来实现。需要付出的代价就是,最后一个调用者需要显式地调用销毁函数DestoryInstance
来避免内存泄漏,示例代码如下所示:
#include <atomic>
#include <cassert>
#include <mutex>
class SingletonForMultithread {
public:
static SingletonForMultithread* GetInstance() {
if (!instance_.load(std::memory_order_acquire)) {
instance_.store(new SingletonForMultithread, std::memory_order_release);
}
return instance_.load(std::memory_order_relaxed);
}
static void DestoryInstance() {
if (instance_.load(std::memory_order_acquire)) {
auto* ptr = instance_.exchange(nullptr, std::memory_order_release);
delete ptr;
ptr = nullptr;
}
}
private:
SingletonForMultithread() = default;
~SingletonForMultithread() = default;
SingletonForMultithread(const SingletonForMultithread&) = delete;
SingletonForMultithread& operator=(const SingletonForMultithread&) = delete;
SingletonForMultithread(SingletonForMultithread&&) = delete;
SingletonForMultithread& operator=(SingletonForMultithread&&) = delete;
private:
static std::atomic<SingletonForMultithread*> instance_;
};
std::atomic<SingletonForMultithread*> SingletonForMultithread::instance_;
int main() {
auto* singleton = SingletonForMultithread::GetInstance();
assert(singleton != nullptr);
singleton->DestoryInstance();
return 0;
}
三、尺寸较大的类单例对象创建(不用显式调用销毁函数来避免内存泄漏)
很多时候,我们无法显式地调用销毁函数来避免内存泄漏,这时就可借助std::unique_ptr<T>
和std::call_once
来实现,示例代码如下:
#include <cassert>
#include <memory>
#include <mutex>
class SingletonForMultithread {
public:
~SingletonForMultithread() = default;
static SingletonForMultithread* GetInstance() {
static std::unique_ptr<SingletonForMultithread> instance;
static std::once_flag only_once;
std::call_once(only_once,
[]() { instance.reset(new SingletonForMultithread); });
return instance.get();
}
private:
SingletonForMultithread() = default;
SingletonForMultithread(const SingletonForMultithread&) = delete;
SingletonForMultithread& operator=(const SingletonForMultithread&) = delete;
SingletonForMultithread(SingletonForMultithread&&) = delete;
SingletonForMultithread& operator=(SingletonForMultithread&&) = delete;
};
int main() {
auto* singleton = SingletonForMultithread::GetInstance();
assert(singleton != nullptr);
return 0;
}
但我在Ubuntu 20.04
系统上使用GCC 9.4.0
似乎无法正常完成任务,会抛出异常,产生core dump
,原因暂不详。