目录

1.知识回顾: 线程安全

2.设计模式的定义

3.单例模式

定义

饿汉单例模式、懒汉单例模式

懒汉单例模式"延时加载"的思想

懒汉单例模式不是线程安全的!

提问: malloc的延迟分配是不是懒汉单例模式?

4.将线程池改成懒汉单例模式


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;
}

运行结果:

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐