Linux多线程(线程池与单例模式)

举报
卖寂寞的小男孩 发表于 2022/10/24 09:13:47 2022/10/24
【摘要】 本文主要介绍线程池与单例模式

@[toc]

一、内存池

在这里插入图片描述

当用户调用new等函数的时候,会向内核申请空间。每调用一次申请一次空间而且在空间不够的时候还会执行内存算法等,需要花费时间。因此在程序创建之初,OS索性直接将一大块内存分配给用户。至于什么时候释放,怎么用都由用户自己决定。这就是内存池的概念。
内存池的目的是为了提高效率,线程池也同理。
因此我们可以类比出线程池的概念,即提前创建一批线程,以便于随时处理任务。

二、线程池的实现

在这里插入图片描述
一个线程池除了包含大量线程之外,还包含一个任务队列。有一个生产任务的线程将任务传入任务队列中,线程池中的线程从任务队列中拿到任务,并进行处理。

1.任务文件

#include<iostream>
using namespace std;
namespace ns_task
{
    class Task
    {
    private:
        int x_;
        int y_;
        char op_;
    public:
        Task(int x,int y,char op):x_(x),y_(y),op_(op)
        {};
        Task()
        {};
        int Run()
        {
            int res=0;
            switch(op_)
            {
            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_<<op_<<y_<<"="<<res<<endl;
        }
    };
}

2.主函数

#include"thread_pool.hpp"
using namespace ns_threadpool;
int main()
{
    ThreadPool<Task>* tp=new ThreadPool<Task>();
    srand((long long)time(nullptr));
    tp->InitThreadPool();
    while(true)
    {
        int x=rand()%20+1;
        int y=rand()%10+1;
        char arr[]="+-*/%";
        char op=arr[rand()%1+4];
        Task t(x,y,op);
        tp->PushTask(t);
        sleep(1);
    }
}

3.线程池

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<queue>
#include"Task.hpp"
using namespace ns_task;
namespace ns_threadpool
{
    template<class T>
    class ThreadPool
    {
        private:
        int num_;
        queue<T> task_queue_;
        pthread_mutex_t mtx;
        pthread_cond_t cond;
        public:
        ThreadPool(int num=5):num_(num)
        {
            pthread_mutex_init(&mtx,nullptr);
            pthread_cond_init(&cond,nullptr);
        }
        bool IsEmpty()
        {
            return task_queue_.empty();
        }
        void lock()
        {
            pthread_mutex_lock(&mtx);
        }
        void unlock()
        {
            pthread_mutex_unlock(&mtx);
        }
        void Wait()
        {
            pthread_cond_wait(&cond,&mtx);
        }
        void Wakeup()
        {
            pthread_cond_signal(&cond);
        }
        void PushTask(const T& in)
        {
            lock();
            task_queue_.push(in);
            unlock();
            Wakeup();
        }
        void PopTask(T* out)
        {
            *out=task_queue_.front();
            task_queue_.pop();
        }
        static void* Routine(void* args)
        {
            pthread_detach(pthread_self());
            ThreadPool<T>* tp=(ThreadPool<T>*)args;
            while(true)
            {
                tp->lock();
                if(tp->IsEmpty())
                {
                    tp->Wait();
                }
                T t;
                tp->PopTask(&t);
                tp->unlock();
                t.Run();
            }
        }
        void InitThreadPool()
        {
            pthread_t tid;
            for(int i=0;i<num_;i++)
            {
                pthread_create(&tid,nullptr,Routine,(void*)this);
            }
        }
        ~ThreadPool()
        {
            pthread_mutex_destroy(&mtx);
            pthread_cond_destroy(&cond);
        }
    };
}

三、单例模式

单例模式即只让对象在内存中存在一份,即一个类只定义一个对象,对于线程池来说只有一个线程池就够了。因此线程池的定义可以使用单例模式。
一般而言,需要采用单例模式的情景是:

1.语义上只需要一个对象。
2.该对象内部存在大量空间保存大量数据,若存在多份(或各种拷贝),内存中就存冗余数据。

1.懒汉式

template<class T>
class Singleton
{
	static T* inst;
public:
	static T* GetInstance()
	{
		if(inst==NULL)
		{
			inst=new T();
		}
		return inst;
	}	
};

懒汉式的做法是,当需要使用对象的时候再创建一个对象。

2.饿汉式

template<class T>
class Singleton
{
	static T data;
public:
	static T* GetInstance()
	{
		return &data;
	}	
};

饿汉式表现为,当创建这个类的时候,对象就已经创建好了,可以随时使用。

3.线程安全

由于单例本身会在任何场景,任何环境下被调用。因此可能会导致Getinstance被重入而产生线程安全问题。
我们可以使用单例模式改写线程池的代码:
此时需要将构造函数设为私有,只能调用getinstance函数来获得类。
首先我们需要定义一个静态的线程池变量以及静态的线程池方法,我们使用懒汉式来实现。
当一个线程进入getinstance函数时,要创建变量,但是被切走了,此时其他线程进入,就会导致线程安全的问题,因此需要进行加锁的操作。

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<queue>
#include"Task.hpp"
using namespace ns_task;
namespace ns_threadpool
{
    template<class T>
    class ThreadPool
    {
        private:
        static ThreadPool<T>* ins;
        int num_;
        queue<T> task_queue_;
        pthread_mutex_t mtx;
        pthread_cond_t cond;  
        ThreadPool(int num=5):num_(num)
        {
            pthread_mutex_init(&mtx,nullptr);
            pthread_cond_init(&cond,nullptr);
        }
        ThreadPool(const ThreadPool<T>&tp)=delete;
        ThreadPool<T> &operator=(ThreadPool<T>& tp)=delete;
        public:
        static ThreadPool<T>* get_instance()
        {
        if(ins==nullptr)
        {
            static pthread_mutex_t Lock=PTHREAD_MUTEX_INITIALIZER; 
            pthread_mutex_lock(&Lock);
            if(ins==nullptr)
            {
                ins=new ThreadPool<T>();
                ins->InitThreadPool();
            }
            pthread_mutex_unlock(&Lock);
            return ins;        
        }
        }
        bool IsEmpty()
        {
            return task_queue_.empty();
        }
        void lock()
        {
            pthread_mutex_lock(&mtx);
        }
        void unlock()
        {
            pthread_mutex_unlock(&mtx);
        }
        void Wait()
        {
            pthread_cond_wait(&cond,&mtx);
        }
        void Wakeup()
        {
            pthread_cond_signal(&cond);
        }
        void PushTask(const T& in)
        {
            lock();
            task_queue_.push(in);
            unlock();
            Wakeup();
        }
        void PopTask(T* out)
        {
            *out=task_queue_.front();
            task_queue_.pop();
        }
        static void* Routine(void* args)
        {
            pthread_detach(pthread_self());
            ThreadPool<T>* tp=(ThreadPool<T>*)args;
            while(true)
            {
                tp->lock();
                if(tp->IsEmpty())
                {
                    tp->Wait();
                }
                T t;
                tp->PopTask(&t);
                tp->unlock();
                t.Run();
            }
        }
        void InitThreadPool()
        {
            pthread_t tid;
            for(int i=0;i<num_;i++)
            {
                pthread_create(&tid,nullptr,Routine,(void*)this);
            }
        }
        ~ThreadPool()
        {
            pthread_mutex_destroy(&mtx);
            pthread_cond_destroy(&cond);
        }
    };
    template<class T>
    ThreadPool<T>* ThreadPool<T>::ins=nullptr;
}

同时主函数调用静态方法创建线程池:

#include"thread_pool.hpp"
using namespace ns_threadpool;
int main()
{
    srand((long long)time(nullptr));
    while(true)
    {
        int x=rand()%20+1;
        int y=rand()%10+1;
        char arr[]="+-*/%";
        char op=arr[rand()%5];
        Task t(x,y,op);
        ThreadPool<Task>::get_instance()->PushTask(t);
        sleep(1);
    }
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。