Linux的线程池以及单例模式的线程池

256 阅读3分钟

线程池的概念

比如我们去海底捞去吃饭,老板不可能来一个客人招募一个服务员,而是先招募一批服务员来服务即将到来的顾客,线程池也是这样,当我们要使用线程处理数据的时候,如果每来一批数据我们创建一个线程的话,每次创建线程的代价是十分昂贵的,分配内存,陷入内核,列入调度等等,不仅如此这些线程还不方便调度如果我们一次性创建多个线程,再分配线程来处理数据,这样就避免了创建很多线程的开销,而且还方便调度这些线程。

线程池的关键元素

  1. 线程:核心线程和工作线程
  2. 任务队列:用于待执行任务排队
  3. 互斥量:因为每个线程都访问任务队列,这是一个临界资源要被互斥量进行保护
  4. 条件变量:当队列是空的时候,这时线程不能拿数据,所以就需要条件变量进行判断

image.png

各个程序的作用

main.cc:用于创建线程池,并塞进任务

#include "thread_pool.hpp"
#include "task.hpp"
#include <time.h>
#include <cstdlib>
#include <unistd.h>
using namespace ns_task;
using namespace ns_thread_pool;
int main()
{
    thread_pool<Task> *tp = new thread_pool<Task>(10);//创建十个线程
    tp->InitThreadPool();//初始化线程

    // tp->
    srand((long long)time(nullptr));//初始化随机数种子
    while (true)
    {
        Task t(rand() % 20 + 1, rand() % 10 + 1, "+-*/%"[rand() % 5]);//创建任务
        tp->pushTask(t);//塞入任务
        // sleep(1);
    }
}

thread_pool.hpp:包含各种有用的函数

#pragma once
#include<queue>
#include<pthread.h>
#include"task.hpp"
using namespace ns_task;
namespace ns_thread_pool
{
    const int num_default=5;
    template<class T>
    class thread_pool
    {
        private:
            int num_;
            std::queue<T> task_queue;
            pthread_mutex_t mtx_;
            pthread_cond_t cond_;
        public:
         void InitThreadPool()
        {
            //创建一些线程,调度线程,让每个线程去执行任务
            pthread_t tid;
            for (int i = 0; i < num_; i++)
            {
                pthread_create(&tid, nullptr, Rountine, (void *)this /*?*/);
            }
        }
        
        void Lock()
        {
              pthread_mutex_lock(&mtx_);
        }
        void Unlock()
        {
             pthread_mutex_unlock(&mtx_);
        }
        void wait()
        {
            pthread_cond_wait(&cond_,&mtx_);
            
        }
        void signal()
        {
               pthread_cond_signal(&cond_);
        }
         
        
        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();
        }
        bool IsEmpty()
        {
            return task_queue.empty();
        }
        bool IsFull()
        {
            return task_queue.size()==num_;
        }
        //类中的成员函数调用另一个函数调用,会隐式传递一个this指针,要消除这个指针,要设为静态函数才行
        //在类中执行类内成员方法,是不可行的
        
         static void* Rountine(void* args)
        {
            pthread_detach(pthread_self());//线程分离
            thread_pool<T>* tp=(thread_pool<T>*)args;
            while(true)
            {
                tp->Lock();
                while(tp->IsEmpty())
                {
                    tp->wait();
                    //可能出现伪唤醒的情况,所以要用循环判断才行,如果队列是空的先让线程等一下
                   
                }
                T t;
                tp->popTask(&t);//让线程从任务队列中拿任务
                

                tp->Unlock();//这个任务队列是被很多线程访问的,是临界资源,要被互斥量进行保护
                t.Run();
            }   
        }
        
       
        // void Thread_init()
        // {
        //     pthread_t tid;

        //     for(int i=0;i<num_;i++)
        //     {
        //         pthread_create(&tid,nullptr,Rountine,(void*)this);
        //     }
        // }
        thread_pool(int n=num_default):num_(n)
        {   
            pthread_mutex_init(&mtx_,nullptr);
            pthread_cond_init(&cond_,nullptr);
        }
        ~thread_pool()
        {
           pthread_mutex_destroy(&mtx_);
           pthread_cond_destroy(&cond_);
        }
    };
}

task.hpp:用于创建任务的头文件

#pragma once

#include <iostream>
#include <pthread.h>

namespace ns_task
{
    class Task
    {
    private:
        int x_;
        int y_;
        char op_; //+/*/%
    public:
        // void (*callback)();
        Task() {}
        Task(int x, int y, char op) : x_(x), y_(y), op_(op)
        {
        }
        std::string Show()
        {
            std::string message = std::to_string(x_);
            message += op_;
            message += std::to_string(y_);
            message += "=?";
            return message;
        }
        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:
                std::cout << "bug??" << std::endl;
                break;
            }
            std::cout << "当前任务正在被: " << pthread_self() << " 处理: " \
            << x_ << op_ << y_ << "=" << res << std::endl;
            return res;
        }
        int operator()()
        {
            return Run();
        }
        ~Task() {}
    };
}

makfile:自动编译文件

th_pool:main.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f th_pool

单例模式下的线程池

单例的概念

单例实际上就是一种创建对象的模式,这种模式涉及一个类,这种类可以实现创建一个对象,一旦这个对象被创建,那么后续再创建这个对象就不再创建,而是直接返回第一次被创建的对象,这种类提供了一种访问对象的唯一方式。可以直接访问,不需要实例化该类的对象。例如在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这

些数据.同时要注意,因为只能有一个对象被创建,拷贝构造函数和赋值是不被允许的。

对于我们的线程池,每个线程被创建都创建了任务队列,这样就是十分冗余的,我们其实用一个单例的类来管理这些数据就可以了。

懒汉和饿汉模式

懒汉实现单例模式:先把碗放下,然后下一顿用到碗再洗,这样的话就不会产生垃圾对象

template <typename T>

class Singleton {

static T* inst;

public:

static T* GetInstance() {

if (inst == NULL) {

inst = new T();

}

return inst;

}

};

解释:当对象没被创建,这个inst是空指针,那就创建一个对象,要是已经创建了,因为inst是静态的,那么就直接返回就可以了,但是缺点是不支持多线程,会存在线程安全的问题,因为第一次创建inst时要是多个一起创建,那就不安全了

饿汉模式:饿汉很饿,所以等不及,吃完饭立即洗碗,等待下一次吃饭,它的缺点是类创建的时候就初始化对象,产生垃圾对象

template <typename T>

class Singleton {

static T data;

public:

static T* GetInstance() {

return &data;

}

};

线程安全的懒汉线程池

#pragma once
#include<queue>
#include<pthread.h>
#include"task.hpp"
using namespace ns_task;
namespace ns_thread_pool
{
    const int num_default=5;
    template<class T>
    class thread_pool
    {
        private:
            int num_;
            std::queue<T> task_queue;
            pthread_mutex_t mtx_;
            pthread_cond_t cond_;
           static thread_pool<T>* inst; 
           //拷贝构造函数要被删除才行
            thread_pool(int n=num_default):num_(n)
        {   
            pthread_mutex_init(&mtx_,nullptr);
            pthread_cond_init(&cond_,nullptr);
        }
        thread_pool(const thread_pool<T> &tp)=delete;
        thread_pool& operator=(thread_pool<T>& tp)=delete;
        public:

        
        //这里要用static因为类成员函数调用另一个成员函数,要先有一个对象才行
        static thread_pool<T>* Getinstance()
        {
            pthread_mutex_t mt=PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP;

            //双判定可以让后来的线程不用在竞争锁,提高获取单例的效率
            if(inst==nullptr)
            {
                pthread_mutex_lock(&mt);
                 if(inst==nullptr)
            {
                inst=new thread_pool<T>();
                inst->InitThreadPool();
            }
            pthread_mutex_unlock(&mt);
            }
           
            return inst;
        }
         void InitThreadPool()
        {
            pthread_t tid;
            for (int i = 0; i < num_; i++)
            {
                pthread_create(&tid, nullptr, Rountine, (void *)this /*?*/);
            }
        }
        
        void Lock()
        {
              pthread_mutex_lock(&mtx_);
        }
        void Unlock()
        {
             pthread_mutex_unlock(&mtx_);
        }
        void wait()
        {
            pthread_cond_wait(&cond_,&mtx_);
            
        }
        void signal()
        {
               pthread_cond_signal(&cond_);
        }
         
        
        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();
        }
        bool IsEmpty()
        {
            return task_queue.empty();
        }
        bool IsFull()
        {
            return task_queue.size()==num_;
        }
        //类中的成员函数调用另一个函数调用,会隐式传递一个this指针,要消除这个指针,要设为静态函数才行
        //在类中执行类内成员方法,是不可行的
        
         static void* Rountine(void* args)
        {
            pthread_detach(pthread_self());
            thread_pool<T>* tp=(thread_pool<T>*)args;
            while(true)
            {
                tp->Lock();
                while(tp->IsEmpty())
                {
                    tp->wait();
                    //可能出现伪唤醒的情况
                   
                }
                T t;
                tp->popTask(&t);//从任务队列中拿任务
                

                tp->Unlock();
                t.Run();
            }   
        }
        
       
        // void Thread_init()
        // {
        //     pthread_t tid;

        //     for(int i=0;i<num_;i++)
        //     {
        //         pthread_create(&tid,nullptr,Rountine,(void*)this);
        //     }
        // }
       
        ~thread_pool()
        {
           pthread_mutex_destroy(&mtx_);
           pthread_cond_destroy(&cond_);
        }
    };
    template<class T>
    thread_pool<T> *thread_pool<T>::inst=nullptr;
    //静态成员要在类外初始化
}
#include "thread_pool.hpp"
#include "task.hpp"
#include <time.h>
#include <cstdlib>
#include <unistd.h>
using namespace ns_task;
using namespace ns_thread_pool;
int main()
{

    std::cout<<"当前程序在执行其他代码"<<std::endl;
    std::cout<<"当前程序在执行其他代码"<<std::endl;
    std::cout<<"当前程序在执行其他代码"<<std::endl;
    std::cout<<"当前程序在执行其他代码"<<std::endl;

    //thread_pool<Task> *tp = new thread_pool<Task>(10);
    //tp->InitThreadPool();

    // tp->
    srand((long long)time(nullptr));
    while (true)
    {
        Task t(rand() % 20 + 1, rand() % 10 + 1, "+-*/%"[rand() % 5]);
        thread_pool<Task>::Getinstance()->pushTask(t);
        //tp->pushTask(t);
        // sleep(1);
    }
}