Linux之多线程第六部分:单例模式

单例模式概念

单例模式是一种 "经典的, 常用的, 常考的 " 设计模式。
设计模式就是针对不同的情况有与之对应的方法,通俗易懂。

单例模式特点

某些类, 只应该具有一个对象(实例), 就称之为单例。
在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据。

什么情况下需要单例模式呢?
一般而言,语义上只需要一个,且该对象内部存在大量空间,保存了大量的数据,如果允许拷贝的话,内存中会存在冗余数据。

**总结来说就是:只创建一次,加载一次。**那么什么时候创建或加载呢?有以下两种模式:

饿汉实现方式和懒汉实现方式

饿汉实现方式:在还不需要用到该对象的时候就已经提前创建好了。
懒汉实现方式:什么时候要用了再创建。

举例:将上一部分学习的线程池例子设计成单例模式、

.hpp

#pragma once
#include 
using namespace std;
#include 
#include 
#include 
namespace ns_thread
{
    const int default_num = 5;
    pthread_mutex_t mtx;
    pthread_cond_t c_cnd;

    template <class T>
    class ThreadPool
    {
    private:
        //线程池中线程个数
        int _num;
        queue<T> task_queue;

        //静态成员变量要在类外初始化!!
        static ThreadPool<T> *ins;

        //单例模式,构造函数必须得实现,但是必须的私有化
        ThreadPool(int num = default_num)
            : _num(num)
        {
            pthread_mutex_init(&mtx, nullptr);
            pthread_cond_init(&c_cnd, nullptr);
        }
        //拷贝构造
        ThreadPool(const ThreadPool<T> &tp) = delete;
        ThreadPool<T> &operator=(ThreadPool<T> &tp) = delete;



    public:
        void Lock()
        {
            pthread_mutex_lock(&mtx);
        }
        void Unlock()
        {
            pthread_mutex_unlock(&mtx);
        }
        void Wait()
        {
            pthread_cond_wait(&c_cnd, &mtx);
        }
        void WakeUp()
        {
            pthread_cond_signal(&c_cnd);
        }
        bool IsEmpty()
        {
            return task_queue.empty();
        }

    public:
        static ThreadPool<T> *GetInstance()
        {
            static pthread_mutex_t _lock = PTHREAD_MUTEX_INITIALIZER;

            //双判断,减少锁的征用,提高获取单例的效率
            if (ins == nullptr)
            {
                pthread_mutex_lock(&_lock);
                if (ins == nullptr)
                {
                    //初始化
                    ins = new ThreadPool<T>();
                    ins->InitThreadPool();
                    cout << "首次加载对象" << endl;
                }
                pthread_mutex_unlock(&_lock);
            }

            return ins;
        }

        // 在类中要让线程执行类内成员方法,是不可行的!!因为会有隐藏this指针就不符合创建线程的格式了
        // 必须让线程执行静态方法,静态成员函数无法访问私有属性
        static void *Rountine(void *args)
        {
            pthread_detach(pthread_self());
            ThreadPool<T> *tp = (ThreadPool<T> *)args;
            while (true)
            {
                tp->Lock();
                //加while判断是防止伪唤醒问题:即两个线程都被唤醒了但只有一个拿到了锁,另一个被唤醒因为没抢到锁本该继续挂起等待,
                //但因为是if也继续执行下去了.		
                while (tp->IsEmpty())
                {
                    //挂起等待
                    tp->Wait();
                }
                //处理任务
                T t;
                tp->Pop(&t);
                tp->Unlock();
                //这里先释放锁再进行任务处理,能达到一个线程处理任务,另一个线程可以抢锁获取数据,达到了并行的效果。
                //如果先处理任务再释放锁,那么每个线程处理任务就是串行的,效率肯定没有并行的高
                t.Run();
            }
        }

        void InitThreadPool()
        {
            pthread_t tid;
            for (int i = 0; i < _num; i++)
            {
                pthread_create(&tid, nullptr, Rountine, (void *)this);
            }
        }

        void Push(const T &in)
        {
            Lock();
            task_queue.push(in);
            Unlock();
            //唤醒线程处理任务
            WakeUp();
        }
		//因为使用Pop函数Rountine已经加锁了,所以不能再Pop函数里再加锁,会造成死锁
        void Pop(T *out)
        {
            *out = task_queue.front();
            task_queue.pop();
        }

        ~ThreadPool()
        {
            pthread_mutex_destroy(&mtx);
            pthread_cond_destroy(&c_cnd);
        }
    };
    template <class T>
    ThreadPool<T> *ThreadPool<T>::ins = nullptr;
}

.cpp

#include 
#include "thread_pool.hpp"
#include "task.hpp"
#include 
using namespace std;
using namespace ns_thread;
using namespace ns_task;

int main()
{
    srand((long long)time(nullptr));
    //因为构造函数设置为私有的就无法初始化了
    // ThreadPool *tp = new ThreadPool();
    // tp->InitThreadPool();
    std::cout << "当前正在运行我的进程其他代码..." << std::endl;
    std::cout << "当前正在运行我的进程其他代码..." << std::endl;
    std::cout << "当前正在运行我的进程其他代码..." << std::endl;
    std::cout << "当前正在运行我的进程其他代码..." << std::endl;
    std::cout << "当前正在运行我的进程其他代码..." << std::endl;
    std::cout << "当前正在运行我的进程其他代码..." << std::endl;
    std::cout << "当前正在运行我的进程其他代码..." << std::endl;
    while (true)
    {
        sleep(1);

        Task t(rand() % 20 + 1, rand() % 10 + 1, "+-*/%"[rand() % 5]);
        ThreadPool<Task>::GetInstance()->Push(t);
		
		//这里会输出ins的地址来展示单例模式的效果
        std::cout << ThreadPool<Task>::GetInstance() << std::endl;
    }
    return 0;
}

tack.hpp

#pragma once
#include
using namespace std;
#include
//实现+-*/%
namespace ns_task
{
    class Task
    {
    private:
        int _x;
        int _y;
        char _c;

    public:
        Task(){};
        Task(int x, int y, char c)
            : _x(x), _y(y), _c(c)
        {
        }
        ~Task(){};

        void Run()
        {
            int res = 0;
            switch (_c)
            {
            case '+':
                res = _x + _y;
                break;
            case '-':
                res = _x - _y;
                break;
            case '*':
                res = _x * _y;
                break;
            case '/':
                res = _x / _y;
                break;
            case '%':
                res = _x % _y;
                break;
            default:
                cout<<"bug??"<<endl;
                break;
            }

            cout<<"当前消费者"<< pthread_self()<<"完成了任务:"<<_x<<_c<<_y<<" = "<<res<<endl;
        }
    };
}

运行结果如下:
Linux之多线程第六部分:单例模式_第1张图片
注意:设计成单例模式,构造函数必须得实现,但是必须的私有化,这样就避免了允许拷贝的情况,这是实现单例模式的基本原则。
通过运行结果可以看出,单例模式下使用的线程池只创建了一次,之后每次使用都用的是原来的,没有拷贝复制的情况,避免了数据冗余

你可能感兴趣的:(单例模式,linux,c++)