线程安全的单例模式-以C++代码为例

本文描述3种场景下的单例模式:

  • 进程体内无线程的单例模式
  • 进程体内多线程单例模式
  • 在单个线程体中的单例模式

本文所写单例模式代码都使用懒汉模式。

进程体内单例

注意问题:

  • 如果进程体中运行多个线程,则需要考虑多线程同步访问的问题。
  • 如果进程体重没有运行多个线程,则不需要考虑多线程同步访问。
  • 使用线程同步锁保证多进程同步

使用场景举例 :

  • 日志类、文件读写类
  • 资源管理类

代码示例:

进程体内没有运行多线程的单例模式,无需考虑线程同步与互斥

class Singleton {
  public:
    static Singleton* getInstance() {
        if (NULL == instance) {
          instance = new SingletonInside();
        }
        return instance;
    }
  private:
    SingletonInside(){}
    ~SingletonInside() {}
    static Singleton* instance;
};

Singleton::instance = NULL;    

进程体内运行多线程单例模式,使用系统mutex保证线程安全

class Singleton {
  public:
    static Singleton* getInstance() {
        pthread_once(&g_once_control, InitOnce);
        pthread_mutex_lock(&mutex);  // lock
        if (NULL == instance) {
          instance = new SingletonInside();
        }
        pthread_mutex_unlock(&mutex); // unlock
        return instance;
    }
  private:
    SingletonInside() {

    }
    ~SingletonInside() {
       pthread_mutex_destroy(&mutex);   // destroy lock
    }
    static void InitOnce(void) {
      pthread_mutex_init(&mutex,NULL);  // init lock
    }
    Singleton* instance;
    static pthread_once_t g_once_control;
    static pthread_mutex_t mutex;
};
Singleton::instance = NULL;
pthread_once_t Singleton::g_once_control = PTHREAD_ONCE_INIT;

单个线程体中的单例

某些资源在单个线程体内需要保持单例,即每个线程体内都保持唯一。每个线程体内,对象相互隔离,则无需考虑线程安全问题。

此种单例模式的实例需要调用系统提供的 TLS 接口,放于线程局部存储中。

使用场景举例:

  • 多路复用Socket封装类
  • 网络上下文环境类
  • 线程安全的资源

代码示例

class Singleton {
  public:
    static Singleton* getInstance() {
       pthread_once(&g_once_control, InitOnce);
       Singleton* instance = (Singleton*)pthread_getspecific(g_thread_data_key);

        if (NULL == instance) {
          instance = new SingletonInside();
          pthread_setspecific(g_thread_data_key, (void*)Singleton)
        }
        return instance;
    }
  private:
    SingletonInside() {}
    ~SingletonInside() {
       pthread_key_delete(g_thread_data_key);
    }
    static void InitOnce(void) {
      pthread_key_create(&g_thread_data_key, NULL);
    }

    static pthread_once_t g_once_control;
    static pthread_key_t g_thread_data_key;
};

pthread_once_t Singleton::g_once_control = PTHREAD_ONCE_INIT;
pthread_key_t Singleton::g_thread_data_key;

如果使用 Poco库 TreadLocal ,代码还会简洁很多,性能上也会好很多

    class Singleton {
      public:
        static Singleton* getInstance() {
            if (NULL == instance) {
              instance = new SingletonInside();
            }
            return instance;
        }
      private:
        SingletonInside() {}
        ~SingletonInside() {
           delete instance;
        }
        static Poco::ThreadLocal<Singleton> instance;
    };
Poco::ThreadLocal<singleton> Singleton::instance = NULL;

总结

  • 无论程序是多进程运行还是多线程运行,代码都要尽量兼容线程安全

Service Locator Patten 总结

核心思想

将构建依赖的接口彻底与依赖者分离,并将此依赖作为“服务”绑定到一个标识符,而后依赖者则可通过这个标识符获取被绑定的依赖。

优点

  • 上层逻辑不依赖于服务接口的具体实现,实现解耦
  • 提高系统的可扩展性
  • 逻辑分层更加简洁清晰,使不同层次的开发人员各司其职,提高团队开发效率

适用场景

与 (Abstract) Factory Patten 比较

  • Factory Patten 创建的所有结果对象多有统一的抽象接口的(更具一般性的)对象
  • Service Locator Patten 创建的服务,异构性程度比较大,接口缺少一般性
  • Factory Patten 单纯为创建对象
  • Service Locator Patten 职能更多些,包括创建服务、配置服务、注入服务的依赖等等

与 Dependence Injection Container 配合

  • Service Locator Patten 和 Dependence Injection并不互斥
  • Service Locator Patten 在定位服务时,创建服务、配置服务、注入服务的依赖通过Dependence Injection Container实现,可以进一步解耦

与 Proxy Patten 配合

  • 对创建的服务进行抽象,增强服务的一般性(即抽象出统一接口)

与 Singleton Patten 配合

  • 承担特定职责的Service Locator的对象无需多次实例化,节省开销
  • 对Service Locator定位的服务缓存做集中存储,存储数据结构存于Service Locator的单例对象即可

优化和扩展

  • 与多种设计模式配合使用
  • 在Service Locator类里定义服务实例化对象的缓冲池,使服务只实例化1次,节省开销

代码示例