线程安全的单例模式

单例模式(Singleton Pattern)是一种设计模式,旨在确保一个类只有一个实例,并提供全局访问点。

单例模式的优点

  • 控制实例化
    单例模式确保某个类只有一个实例,这对于需要集中管理、全局共享资源的场景非常有用。例如,数据库连接、线程池、日志管理器等资源的共享。
  • 全局访问
    单例模式提供了全局访问点,任何地方都能通过该点访问到该对象的唯一实例。这使得在不同的类和模块中能够共享同一个对象,而无需传递实例引用。
  • 确保一致性
    由于只有一个实例,单例模式能够确保状态的一致性。在多个线程或多个请求中,所有对该实例的修改都会反映到同一个实例中,避免了数据不一致的问题

适用场景

单例模式适合用在以下几种情况:

  • 系统中需要频繁使用某个对象,且该对象的创建代价较高。
  • 需要全局访问某个对象,而不希望它被多次创建。
  • 系统中有共享的资源需要进行集中管理(例如配置管理、线程池等)。

示例

  • CMakeLists.txt
bash 复制代码
cmake_minimum_required(VERSION 3.20)

project(singleton_test)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)

file(GLOB SOURCES
    "main.cpp"
    "singleton.h"
    "random_number_generator.h"
    "random_number_generator.cpp")

add_executable(singleton_test ${SOURCES})
  • main.cpp
cpp 复制代码
#include <iostream>

#include "random_number_generator.h"

using singleton::randomNum;

int main()
{
    randomNum().start();

    return 0;
}
  • random_number_generator.h
cpp 复制代码
#ifndef RANDOM_NUMBER_GENERATOR_H
#define RANDOM_NUMBER_GENERATOR_H

#include <random>

#include "singleton.h"

namespace singleton {

class RandomNumberGenerator
{
public:
    // 启动方法,开始每隔 3 秒生成一个随机数
    void start();

private:
    // 构造函数
    RandomNumberGenerator();

    // 随机数生成器和分布
    std::default_random_engine generator;            // 随机数生成器
    std::uniform_int_distribution<int> distribution; // 随机数分布
    friend struct DefaultSingletonTraits<RandomNumberGenerator>;
};

/// 获取单例
inline RandomNumberGenerator &randomNum()
{
    return Singleton<RandomNumberGenerator>::instance();
}

} // namespace singleton

#endif // RANDOM_NUMBER_GENERATOR_H
  • random_number_generator.cpp
      为了确保在Linux系统中整个应用程序只能运行一个实例,并且强制实现单例模式的全局唯一性,使用命名锁(例如文件锁或 POSIX 锁)来确保只有一个进程可以创建和访问单例实例。
      使用 flock(文件锁)来确保在 Linux 系统上同一时间只有一个进程能够访问 RandomNumberGenerator 的单例实例。
      1.创建一个锁文件,通过 flock 来保证文件的独占访问。
      2.锁文件只有在进程成功获得锁时才能继续执行,并创建唯一的 RandomNumberGenerator 实例。
      3.退出时释放文件锁,确保其他进程可以访问文件。
cpp 复制代码
#include "random_number_generator.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <fcntl.h>    // open
#include <sys/file.h> // flock
#include <unistd.h>   // close

namespace singleton {

// 构造函数初始化随机数生成器和分布
RandomNumberGenerator::RandomNumberGenerator() : generator(std::random_device{}()), distribution(1, 100)
{
}

// 文件锁路径,用于确保程序只能运行一个实例
const char *LOCK_FILE_PATH = "/tmp/random_number_generator.lock";

// start() 方法:每隔 3 秒生成一个随机数
void RandomNumberGenerator::start()
{
    // 打开锁文件
    int lock_file = open(LOCK_FILE_PATH, O_CREAT | O_WRONLY, 0666);
    if (lock_file == -1) {
        std::cerr << "Error opening lock file." << std::endl;
        return;
    }

    // 尝试获取文件锁
    if (flock(lock_file, LOCK_EX | LOCK_NB) == -1) {
        std::cerr << "Another instance is already running." << std::endl;
        close(lock_file);
        return;
    }

    std::cout << "Lock acquired. Starting random number generation..." << std::endl;

    // 当获得锁后开始生成随机数
    while (true) {
        int random_number = distribution(generator);
        std::cout << "Random number: " << random_number << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(3));
    }

    // 释放锁并关闭文件
    flock(lock_file, LOCK_UN); // 释放锁
    close(lock_file);          // 关闭文件
}

} // namespace singleton
  • singleton.h
    通过 Singleton 类实现了一个线程安全的单例模式,采用了自旋锁(原子操作)来确保在多线程环境中只有一个线程能够创建单例实例 ,另外通过 std::shared_ptr 自动管理内存。DefaultSingletonTraits 提供了默认的内存管理机制,用户可以根据需要自定义内存分配和销毁策略。
cpp 复制代码
#ifndef SINGLETON_H
#define SINGLETON_H

#include <mutex>
#include <atomic>
#include <memory>

namespace singleton {

// 为 Singleton 类提供默认的内存分配和销毁机制
// Default traits for Singleton<Type>. Calls operator new and operator delete on the object. Registers automatic deletion at process exit.
// Overload if you need arguments or another memory allocation function.
template <typename Type>
struct DefaultSingletonTraits
{
    // Allocates the object.
    static Type *New()
    {
        // The parenthesis is very important here; it forces POD type initialization.
        return new Type();
    }
    // Destroys the object.
    static void Delete(Type *x) { delete x; }
};

template <typename T, typename Traits = DefaultSingletonTraits<T>>
class Singleton
{
public:
	// 返回单例实例,如果实例尚未创建,则会创建它。为了保证线程安全,使用了自旋锁机制(通过 atomic_exchange_explicit 和 atomic_store_explicit)来确保在多线程环境中只有一个线程能够创建单例实例。
    static T &instance()
    {
        if (!instance_) {
            // In the rare event that two threads come into this section only one will acquire the spinlock and build the actual instance
            while (std::atomic_exchange_explicit(&lock_, true, std::memory_order_acquire)) {
                ; // spin until acquired
            }

            if (!instance_) {
                // This is the thread that will build the real instance since
                // it hasn-t been instantiated yet
                instance_ = std::shared_ptr<T>(Traits::New(), [](T *p) { Traits::Delete(p); });
                valid_    = true;
            }

            // Release spinlock
            std::atomic_store_explicit(&lock_, false, std::memory_order_release);
        }

        return *instance_;
    }

    static bool isValid() { return valid_; }

    static void destroy()
    {
        if (instance_) {
            while (std::atomic_exchange_explicit(&lock_, true, std::memory_order_acquire)) {
                ; // spin until acquired
            }
            instance_.reset();
            valid_ = false;
            // Release spinlock
            std::atomic_store_explicit(&lock_, false, std::memory_order_release);
        }
    }

private:
    Singleton();

    static std::atomic<bool> lock_;
    static std::atomic<bool> valid_;
    static std::shared_ptr<T> instance_; // 单例对象的持有者
};

template <typename T, typename Traits>
std::shared_ptr<T> Singleton<T, Traits>::instance_ = nullptr;
template <typename T, typename Traits>
std::atomic<bool> Singleton<T, Traits>::lock_;
template <typename T, typename Traits>
std::atomic<bool> Singleton<T, Traits>::valid_;

} // namespace singleton

#endif // SINGLETON_H
相关推荐
澄澈天空5 分钟前
c++ mfc调用UpdateData(TRUE)时,发生异常
c++·mfc
XZHOUMIN7 分钟前
MFC中如何在工具条动态增加菜单
c++·mfc
R6bandito_19 分钟前
Qt几何数据类型:QLine类型详解(基础向)
c语言·开发语言·c++·经验分享·qt
禊月初三25 分钟前
C++面试突破---C/C++基础
c语言·c++·面试
程序猿阿伟1 小时前
《平衡之策:C++应对人工智能不平衡训练数据的数据增强方法》
前端·javascript·c++
CodeGrindstone1 小时前
Muduo网络库剖析 --- 架构设计
网络·c++·网络协议·tcp/ip
9毫米的幻想1 小时前
【C++】—— set 与 multiset
开发语言·c++·rpc
想成为高手4991 小时前
深入理解AVL树:结构、旋转及C++实现
开发语言·数据结构·c++
£suPerpanda1 小时前
P3916 图的遍历(Tarjan缩点和反向建边)
数据结构·c++·算法·深度优先·图论
黑果果的思考2 小时前
C++看懂并使用-----回调函数
开发语言·c++