我的有篇文章有简单的介绍单例的实现,在其中关于多线程安全问题采取的策略是加锁,最近在阅读muduo源码的时候看到更加好的实现方式通过pthread_once.
pthread_once
在多线程的环境下,有些事仅仅需要执行一次。通常会把这个初始化放在mian函数中,当你写一个库的时候就不能放在main函数中了,这个时候你可以考虑pthread_once
int pthread_once(pthread_once_t* once_control, void(*init_routine)(void));
//本函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅仅执行一次。
在多线程下,尽管pthread_once会被多个线程调用,但是init_routine函数只会执行一次。
template<typename T>
class Singleton
{
public:
static T& instance()
{
pthread_once(&once_control, &Singleton::init);
return *value_;
}
private:
Singleton();
~Singleton();
Singleton& operator=(const Singleton&);
Singleton(const Singleton&);
static void init()
{
value_ = new T();
::atexit(destroy);
}
static void destroy()
{
typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1];
delete value_;
}
static pthread_once_t once_control;
static T* value_;
};
上面代码和muduo基本一样,Singleton是一个模板类。通过instance()返回一个类型为T的对象,它确实通过pthread_once达到多线程安全,通过instance返回的对象总是同一个对象。但是如果对象T是能拷贝构造的呢??????
Singleton<string>::instance();//这里创建对象
string str1 = Singleton<string>::instance();这里重新创建一个对象str1
这明显违背了单例设计模式的初衷,除非你阻止类型T的拷贝构造,但这也没啥意义。就像下面这样,我自己创建一个对象。
Singleton<string>::instance();
string str1("123");
其实我目前不大明白作者设计这个模板的目的,可能是给这个库里面的其他模块使用。但是可以参考作者的思路设计一个专用的Singleton类,如下,这个类的构造函数、拷贝构造、拷贝复制都是私有的,只能通过instance返回一个Singleton对象,而且该对象永远是同一个对象,下面有测试代码,可以自行测试
#include <pthread.h>
#include <stdlib.h> //atexit
#include <thread>
#include <unistd.h>
#include <string>
class Singleton
{
public:
static Singleton& instance()
{
pthread_once(&once_control, &Singleton::init);
return *value_;
}
const std::string& get_name(){return name_;}
const int& get_age(){return age_;}
void init_n_a(std::string name, int age)
{
name_ = name;
age_ = age;
}
void set_name(std::string name){name_ = name;}
void set_age(int age){age_ = age;}
private:
Singleton(){};
~Singleton(){};
Singleton& operator=(const Singleton&);
Singleton(const Singleton&);
static void init()
{
value_ = new Singleton();
::atexit(destroy);
}
static void destroy()
{
delete value_;
}
static pthread_once_t once_control;
static Singleton* value_;
std::string name_;
int age_;
};
pthread_once_t Singleton::once_control = PTHREAD_ONCE_INIT;
Singleton* Singleton::value_ = NULL;
void threadfunc(int i)
{
printf("thread %d start, tid = %d\n", i, std::this_thread::get_id());
sleep(1);
printf("tid = %d, %p\n", std::this_thread::get_id(),&Singleton::instance());
sleep(1);
printf("tid = %d, name = %s, age = %d\n",
std::this_thread::get_id(),
Singleton::instance().get_name().c_str(),
Singleton::instance().get_age());
}
int main()
{
Singleton::instance().init_n_a("xxx", 12);
printf("tid = %d, %p\n", std::this_thread::get_id(),&Singleton::instance());
std::thread t1(threadfunc, 1);
std::thread t2(threadfunc, 2);
std::thread t3(threadfunc, 3);
std::thread t4(threadfunc, 4);
t1.join();
t2.join();
t3.join();
t4.join();
return 0;
}