OS83.【Linux】线程安全的单例模式
目录
1.知识回顾: 线程安全
参见OS75.【Linux】线程互斥(3) 线程安全、重入文章
2.设计模式的定义
简单而言,针对一些经典的常见的场景,给定了一些对应的解决方案,这个就是设计模式,而单例模式就是设计模式的一种
3.单例模式
定义
在某些情景下,某些类只应该具有一个实例化后的对象,就称之为单例.
在很多服务器开发场景中, 经常需要让服务器加载很多的数据到内存中,往往要用一个单例的类来管理这些数据.
饿汉单例模式、懒汉单例模式
饿汉单例模式(eager initialization singleton pattern):
形象理解,饿汉吃完饭,立刻洗碗,因为吃下一顿的时候可以立刻拿着碗就能吃饭,这是饿汉单例模式
对应到计算机中,类加载时就立即创建实例("饥饿"地急于创建)
比如以下代码:
template <class T>
class eager_initialization_singleton_pattern
{
public:
static T* GetInstance()
{
return &data;
}
private:
static T data;//只是声明
};
template<class T>
T eager_initialization_singleton_pattern<T>::data;
static T data是静态成员变量,之前在CD25.【C++ Dev】类和对象(16) static成员(上)文章讲过静态成员变量的6个特点
特点1.static成员为静态成员,为所有类对象所共享(在某种程度上可以理解为全局的,用类去封装"全局变量"),存放在静态区,则不属于某个具体的对象,不能通过初始化列表来初始化
特点2.静态成员变量必须在类外初始化 (const修饰的size_t类型除外),定义时不添加static关键字 ,类中只是声明
特点3:类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
特点4:静态对象只会定义一次
特点5:静态成员也是类的成员,受public、protected、private 访问限定符的限制
特点6:静态成员函数没有this指针,只要指定类域且静态成员变量在public里面就可以访问
程序一旦启动,就自动初始化data,和是否定义eager_initialization_singleton_pattern对象没有关系,但前提是模板已被实例化
测试一下:
template <class T>
class eager_initialization_singleton_pattern
{
public:
static T* get_instance()
{
return &data;
}
private:
static T data;
};
template<class T>
T eager_initialization_singleton_pattern<T>::data;
//显式实例化
template class eager_initialization_singleton_pattern<int>;
int main()
{
return 0;
}
gdb运行结果:

懒汉单例模式(lazy initialization singleton pattern):
形象理解,懒汉吃完饭,碗暂时不洗,然后下一顿饭用到这个碗了再洗碗,就是懒汉单例模式
对应到计算机中,第一次使用时才创建实例("懒惰"地延迟创建)
比如以下代码:
template <class T>
class lazy_initialization_singleton_pattern
{
public:
static T* get_instance()
{
if (inst == NULL)
inst = new T();
return inst;
}
private:
static T* inst;
};
static T* inst是静态成员变量的指针,虽然程序一旦启动,就自动初始化inst,但是inst还没有指向的对象,需要手动调用get_instance成员函数
懒汉单例模式"延时加载"的思想
懒汉方式最核心的思想是"延时加载",优化启动速度,比如上篇文章说的Java ThreadPoolExecutor
官方文档: ThreadPoolExecutor_(Java_SE_25_&_JDK_25)
On-demand construction
By default, even core threads are initially createdand started only when new tasks arrive(任务来了才创建线程执行,即延迟线程的创建,这是懒汉单例模式) , but this can be overridden dynamically using method prestartCoreThread() or prestartAllCoreThreads(). You probably want to prestart threads if you construct the pool with a non-empty queue.
但”延时加载”并没有在整体上提高效率,因为该申请就申请,该写入数据还是要写入数据,也就是说,花费的总时间没有变,只是”延时加载”修改老师时间的分配: 申请和分配实际内存一起做 → 先申请,等需要用了才分配实际内存
懒汉单例模式不是线程安全的!
如果多线程同时调用get_instance,由于线程切换的缘故,可能会创建多份T对象实例,但是后续再次调用,就没有问题了
提问: malloc的延迟分配是不是懒汉单例模式?
malloc的延迟分配指的是: 进程申请内存时,操作系统并不会立即分配实际的物理内存页,而是等到真正访问时才分配物理页
malloc的延迟分配不是懒汉单例模式,malloc的延迟分配是系统层的概念,而懒汉单例模式是应用层的概念
| 层级 | 现象 | 本质 | 控制者 |
|---|---|---|---|
| 应用层 | 懒汉单例模式 | 设计模式:延迟对象的创建 | 程序员主动设计 |
| 系统层 | malloc的延迟分配 | 内存优化:延迟物理页的映射 | 操作系统自动优化 |
4.将线程池改成懒汉单例模式
之前在OS83.【Linux】设计线程池文章讲过线程池,为了方便,这里附上完整代码:
#include <pthread.h>
#include <vector>
#include <string>
#include <iostream>
#include <unistd.h>
#include <queue>
struct thread_worker
{
std::string _name;
pthread_t _tid;
};
template <class T>
class thread_pool
{
public:
thread_pool(int num=5)
{
pthread_mutex_init(&_lock,nullptr);
pthread_cond_init(&_cond,nullptr);
_pool.resize(num);
create_thread();
}
static void* handle_task(void* args)
{
thread_pool<T>* ptr=static_cast<thread_pool<T>*>(args);
pthread_detach(pthread_self());
for (;;)
{
pthread_mutex_lock(&(ptr->_lock));
if ((ptr->_tasks).empty())
{
printf("0x%lX线程: 暂无任务,去等待\n",pthread_self());
ptr->thread_sleep();
}
T t=(ptr->_tasks).front();
(ptr->_tasks).pop();
pthread_mutex_unlock(&(ptr->_lock));
t();
}
return nullptr;
}
void thread_sleep()
{
pthread_cond_wait(&_cond,&_lock);
}
void thread_wakeup()
{
pthread_cond_signal(&_cond);
}
void push(const T& t)
{
pthread_mutex_lock(&_lock);
_tasks.push(t);
thread_wakeup();
pthread_mutex_unlock(&_lock);
}
~thread_pool()
{
pthread_mutex_destroy(&_lock);
pthread_cond_destroy(&_cond);
_pool.clear();
}
private:
void create_thread()
{
for (int i=0;i<_pool.size();i++)
{
std::string name="thread-"+std::to_string(i);
_pool[i]._name=name;
pthread_create(&(_pool[i]._tid),nullptr,handle_task,this);
}
}
std::vector<thread_worker> _pool;
pthread_mutex_t _lock;
pthread_cond_t _cond;
std::queue<T> _tasks;
};
class Task
{
public:
void operator()(void)
{
int i=rand()%100;
printf("0x%lX线程: 执行第%d号任务\n",pthread_self(),i);
}
};
int main()
{
srand((unsigned int)time(0));
thread_pool<Task> tp;
for (;;)
{
Task t;
sleep(1);
tp.push(t);
sleep(1);
}
return 0;
}
需要将线程池改成懒汉单例模式:
std::vector<thread_worker> _pool;需要改成指针类型,等需要用了再实例化
保证是"单例模式",而且线程对象不支持拷贝,拷贝构造、operator=、构造函数都设置为私有,方法都为空
这样做的话,构造函数私有的,无法通过operator()创建对象,但可以指明一个创建实例的公有函数get_instance,类内可以调私有函数:
static void get_instance(thread_pool<T>*& ptr,int num=5)
{
if (ptr==nullptr)
ptr=new thread_pool<T>(num);
}
完整代码:
#include <pthread.h>
#include <vector>
#include <string>
#include <iostream>
#include <unistd.h>
#include <queue>
struct thread_worker
{
std::string _name;
pthread_t _tid;
};
template <class T>
class thread_pool
{
public:
static thread_pool<T>* get_instance(int num=5)
{
if (_instance==nullptr)
_instance=new thread_pool<T>(num);
return _instance;
}
static void* handle_task(void* args)
{
thread_pool<T>* ptr=static_cast<thread_pool<T>*>(args);
pthread_detach(pthread_self());
for (;;)
{
pthread_mutex_lock(&(ptr->_lock));
if ((ptr->_tasks).empty())
{
printf("0x%lX线程: 暂无任务,去等待\n",pthread_self());
ptr->thread_sleep();
}
T t=(ptr->_tasks).front();
(ptr->_tasks).pop();
pthread_mutex_unlock(&(ptr->_lock));
t();
}
return nullptr;
}
void thread_sleep()
{
pthread_cond_wait(&_cond,&_lock);
}
void thread_wakeup()
{
pthread_cond_signal(&_cond);
}
void push(const T& t)
{
pthread_mutex_lock(&_lock);
_tasks.push(t);
thread_wakeup();
pthread_mutex_unlock(&_lock);
}
~thread_pool()
{
pthread_mutex_destroy(&_lock);
pthread_cond_destroy(&_cond);
_pool->clear();
}
private:
void create_thread()
{
for (int i=0;i<_pool->size();i++)
{
std::string name="thread-"+std::to_string(i);
(*_pool)[i]._name=name;
pthread_create(&((*_pool)[i]._tid),nullptr,handle_task,this);
}
}
thread_pool(int num=5)
{
pthread_mutex_init(&_lock,nullptr);
pthread_cond_init(&_cond,nullptr);
_pool=new std::vector<thread_worker>(num);
create_thread();
}
thread_pool(const thread_pool&){}
const thread_pool<T>& operator=(const thread_pool<T>&){}
std::vector<thread_worker>* _pool=nullptr;
pthread_mutex_t _lock;
pthread_cond_t _cond;
std::queue<T> _tasks;
static thread_pool<T>* _instance;
};
class Task
{
public:
void operator()(void)
{
int i=rand()%100;
printf("0x%lX线程: 执行第%d号任务\n",pthread_self(),i);
}
};
template<class T>
thread_pool<T>* thread_pool<T>::_instance = nullptr;
int main()
{
srand((unsigned int)time(0));
thread_pool<Task>* tp = thread_pool<Task>::get_instance();
for (;;)
{
Task t;
sleep(1);
tp->push(t);
sleep(1);
}
return 0;
}
运行结果:

但是上面改不是线程安全的,为了防止多线程同时调用get_instance,需要加锁,可以这样做:
pthread_mutex_t get_instance_lock=PTHREAD_MUTEX_INITIALIZER; //类外定义全局锁
static thread_pool<T>*get_instance(thread_pool<T>*& ptr,int num=5)
{
pthread_mutex_lock(&get_instance_lock);
if (_instance==nullptr)
_instance=new thread_pool<T>(num);
pthread_mutex_unlock(&get_instance_lock);
return _instance;
}
如果像上面这样写,第一个线程申请成功,后面所有的线程虽然申请锁、释放锁是成功的,但申请线程池都会失败,也就是说后面所有的线程不需要申请锁、释放锁,占用系统资源
显然,这样做效率不高,由于是单例模式,只可调用一次get_instance,比如多线程访问get_instance后,其它线程就没有必要访问get_instance了!!!
提高效率的方法: 再加一层判断,双重判定空指针,降低锁冲突的概率,提高性能.
static void get_instance(thread_pool<T>*& ptr,int num=5)
{
if (ptr==nullptr)
{
pthread_mutex_lock(&get_instance_lock);
if (ptr==nullptr)
ptr=new thread_pool<T>(num);
pthread_mutex_unlock(&get_instance_lock);
}
}
测试代码:
#include <pthread.h>
#include <vector>
#include <string>
#include <iostream>
#include <unistd.h>
#include <queue>
struct thread_worker
{
std::string _name;
pthread_t _tid;
};
pthread_mutex_t get_instance_lock=PTHREAD_MUTEX_INITIALIZER;
template <class T>
class thread_pool
{
public:
static thread_pool<T>* get_instance(int num=5)
{
if (_instance==nullptr)
{
pthread_mutex_lock(&get_instance_lock);
printf("0x%lx线程取得了锁\n",pthread_self());
if (_instance==nullptr)
{
_instance=new thread_pool<T>(num);
printf("0x%lx线程成功创建线程池\n",pthread_self());
}
pthread_mutex_unlock(&get_instance_lock);
printf("0x%lx线程释放了锁\n",pthread_self());
}
else
{
printf("0x%lx线程被挡住了\n",pthread_self());
pthread_exit(nullptr);
}
return _instance;
}
static void* handle_task(void* args)
{
thread_pool<T>* ptr=static_cast<thread_pool<T>*>(args);
pthread_detach(pthread_self());
for (;;)
{
pthread_mutex_lock(&(ptr->_lock));
//不能用if
while ((ptr->_tasks).empty())
{
printf("0x%lX线程: 暂无任务,去等待\n",pthread_self());
ptr->thread_sleep();
}
T t=(ptr->_tasks).front();
(ptr->_tasks).pop();
pthread_mutex_unlock(&(ptr->_lock));
t();
}
return nullptr;
}
void thread_sleep()
{
pthread_cond_wait(&_cond,&_lock);
}
void thread_wakeup()
{
pthread_cond_signal(&_cond);
}
void push(const T& t)
{
pthread_mutex_lock(&_lock);
_tasks.push(t);
thread_wakeup();
pthread_mutex_unlock(&_lock);
}
~thread_pool()
{
pthread_mutex_destroy(&_lock);
pthread_cond_destroy(&_cond);
_pool->clear();
}
private:
void create_thread()
{
for (int i=0;i<_pool->size();i++)
{
std::string name="thread-"+std::to_string(i);
(*_pool)[i]._name=name;
pthread_create(&((*_pool)[i]._tid),nullptr,handle_task,this);
}
}
thread_pool(int num=5)
{
pthread_mutex_init(&_lock,nullptr);
pthread_cond_init(&_cond,nullptr);
_pool=new std::vector<thread_worker>(num);
create_thread();
}
thread_pool(const thread_pool&){}
const thread_pool<T>& operator=(const thread_pool<T>&){}
std::vector<thread_worker>* _pool=nullptr;
pthread_mutex_t _lock;
pthread_cond_t _cond;
std::queue<T> _tasks;
static thread_pool<T>* _instance;
};
class Task
{
public:
void operator()(void)
{
int i=rand()%100;
printf("0x%lX线程: 执行第%d号任务\n",pthread_self(),i);
}
};
template<class T>
thread_pool<T>* thread_pool<T>::_instance = nullptr;
void* mt_get_instance(void * args)
{
thread_pool<Task>* tp=static_cast<thread_pool<Task>*>(args);
pthread_detach(pthread_self());
tp = thread_pool<Task>::get_instance();
for (;;)
{
Task t;
sleep(1);
tp->push(t);
sleep(1);
}
return nullptr;
}
int main()
{
srand((unsigned int)time(0));
thread_pool<Task>* tp=nullptr;
for (int i=0;i<5;i++)
{
pthread_t tid;
pthread_create(&tid,nullptr,mt_get_instance,tp);
}
for(;;)
{
sleep(100);
}
return 0;
}
运行结果:

更多推荐
所有评论(0)