一个单例类的特点:
1、进程内只有唯一的类实例
不可以被随意构造(禁止任何形式的自行构造),在被使用时或使用前创建(前者为所谓懒汉式,后者为所谓饿汉式),进程结束时释放
2、多线程共用
内部资源访问控制需要锁互斥
具体的实现:
1、实现禁止构造:
出于
不可以被随意构造(禁止任何形式的自行构造)的需要,单例基类需要禁止构造,实现方式为继承一个noncopyable基类
可以像下面这样的实现一个noncopyable,也可以继承boost的boost::noncopyable,推荐下面的方式。
下面的方式也是c++11对delete关键字的运用,禁止掉默认、复制构造函数和赋值运算符方法,使程序中无法创建单例类的实例。
class noncopyable {
public:
noncopyable() = delete;
noncopyable(const noncopyable &) = delete;
noncopyable &operator= (const noncopyable &) = delete;
~noncopyable() = default;
};
按常用的懒汉式,一个单例基类的基本特性:
1. 在被使用时创建,多个线程使用也只会创建一次;
2. 进程退出时,释放掉实例
基于以上特性需要,实现方式分别为:
1. 使用pthread_once_t数据结构,保证在第一次使用时,多个线程只会有一个线程实际创建单例实例,其余线程则直接获取创建好的实例
2. 让每个单例类实例以静态指针变量存在,并重载类静态资源析构函数(__attribute__((destruct))修饰),这样,在进程退出自动调用静态变量析构方法时,释放掉单例内存
具体实现如下,
1. 模板类Singleton继承noncopyable实现禁止构造
2. public方法仅包括get,进程内线程只可以通过get方法获取单例类实例
3. 单例实例为类静态变量_instance,即每个单例类都有一个唯一的静态类实例
4. 同时每个单例类由静态变量_p_once控制只有一个线程会在单例类实例不存在时创建实例(调用类静态函数_construct),其他线程直接获取实例
5. 重载类的静态资源析构函数(__attribute__((destruct))修饰),在进程退出时,释放掉单例实例
template<class T> class Singleton : public noncopyable {
Singleton(){}
~Singleton(){}
static void _construct () {
_instance = new T;
}
__attribute__((destruct)) static void _delete () {
delete _instance;
}
static T *_instance;
static pthread_once_t _p_once;
public:
static T * get () {
pthread_once(&Singleton::_p_once, &Singleton::_construct);
return _instance;
}
};
template<class T> pthread_once_t Singleton<T>::_p_once = PTHREAD_ONCE_INIT;
template<class T> T *Singleton<T>::_instance = nullptr;
3、实现一个单例类:
多线程均通过get方法获取单例实例即可。
由于多线程均操作同一个实例,所以单例类在资源非原子操作时要加互斥锁,如下:
class Resource {
int a;
std::atomic<int> b;
std::mutex mtx;
public:
Resource(){}
~Resource(){}
void Init () {
std::cout << "init" << std::endl;
}
void Set (int _a) {
std::unique_lock<std::mutex> lock(mtx);
a = _a;
}
int Get () {
int t;
{
std::unique_lock<std::mutex> lock(mtx);
t = a;
}
return t;
}
void SetAtomic (int _b) {
b = _b;
}
int GetAtomic () {
return b;
}
};
4、测试:
std::vector<std::thread> ths(10);
for (int i = 0; i < 10; i++) {
ths[i] = std::thread([i] () {
while (1) {
std::random_device rd;
int a = rd() % 100, b = rd() % 100;
Resource *s = Singleton<Resource>::get();
int rawa = s->Get(), rawb = s->GetAtomic();
s->Set(a);
s->SetAtomic(b);
std::cout << "i am thread " << i << " get rawa " << rawa << " and get rawb " << rawb << " , set a " << a << " and set b " << b << "-----------------" << std::endl;
std::chrono::microseconds duration(100);
std::this_thread::sleep_for(duration);
}
});
}
for (auto &i:ths) {
i.join();
}
10个线程并发的读写单例类Resource的单例。
下图是测试的输出情况,验证是否发生多线程同步问题。
i am thread 5 get rawa 40 and get rawb 38 , set a 49 and set b 65-----------------
i am thread 1 get rawa 49 and get rawb 65 , set a 43 and set b 25-----------------
i am thread 3 get rawa 43 and get rawb 25 , set a 14 and set b 87-----------------
i am thread 8 get rawa 14 and get rawb 87 , set a 46 and set b 54-----------------
i am thread 7 get rawa 46 and get rawb 54 , set a 63 and set b 0-----------------
i am thread 6 get rawa 63 and get rawb 0 , set a 21 and set b 26-----------------
i am thread 5 get rawa 21 and get rawb 26 , set a 35 and set b 3-----------------
i am thread 9 get rawa 35 and get rawb 3 , set a 57 and set b 39-----------------
i am thread 2 get rawa 57 and get rawb 39 , set a 24 and set b 16-----------------
i am thread 0 get rawa 24 and get rawb 16 , set a 94 and set b 65-----------------
i am thread 1 get rawa 94 and get rawb 65 , set a 30 and set b 0-----------------
i am thread 3 get rawa 30 and get rawb 0 , set a 76 and set b 35-----------------
i am thread 8 get rawa 76 and get rawb 35 , set a 46 and set b 14-----------------
i am thread 7 get rawa 46 and get rawb 14 , set a 93 and set b 47-----------------
i am thread 6 get rawa 93 and get rawb 47 , set a 56 and set b 90-----------------
i am thread 4 get rawa 56 and get rawb 90 , set a 81 and set b 35-----------------
i am thread 5 get rawa 81 and get rawb 35 , set a 74 and set b 20-----------------
i am thread 9 get rawa 74 and get rawb 20 , set a 65 and set b 0-----------------
i am thread 2 get rawa 65 and get rawb 0 , set a 56 and set b 62-----------------
i am thread 0 get rawa 56 and get rawb 62 , set a 70 and set b 45-----------------
i am thread 1 get rawa 70 and get rawb 45 , set a 39 and set b 62-----------------
i am thread 3 get rawa 39 and get rawb 62 , set a 34 and set b 90-----------------
i am thread 8 get rawa 34 and get rawb 90 , set a 33 and set b 57-----------------
i am thread 7 get rawa 33 and get rawb 57 , set a 47 and set b 6-----------------
i am thread 6 get rawa 47 and get rawb 6 , set a 6 and set b 1-----------------
i am thread 4 get rawa 6 and get rawb 1 , set a 22 and set b 8-----------------
i am thread 5 get rawa 22 and get rawb 8 , set a 65 and set b 72-----------------
i am thread 9 get rawa 65 and get rawb 72 , set a 54 and set b 96-----------------
i am thread 2 get rawa 54 and get rawb 96 , set a 79 and set b 59-----------------
i am thread 0 get rawa 79 and get rawb 59 , set a 19 and set b 48-----------------
i am thread 1 get rawa 19 and get rawb 48 , set a 99 and set b 12-----------------
i am thread 3 get rawa 99 and get rawb 12 , set a 9 and set b 69-----------------
i am thread 8 get rawa 9 and get rawb 69 , set a 97 and set b 16-----------------
i am thread 7 get rawa 97 and get rawb 16 , set a 79 and set b 46-----------------
i am thread 4 get rawa 73 and get rawb 95 , set a 45 and set b 7-----------------
i am thread 5 get rawa 45 and get rawb 7 , set a 71 and set b 66-----------------