手把手教你用C++實現一個簡易的線程池

線程池的概念

什麼是線程池

顧名思義,線程池就是一個有很多空閒線程的池子(線程的數量受到限制),需要用到多執行流進行任務處理時,就從池子中喚醒一個線程去處理任務

線程池的優點
  • 避免大量線程頻繁創建和銷燬帶來的時間成本
    :如果在一開始即創建好線程,要用的時候直接從線程池中取出,用完再放回,這樣就大大減少了創建與銷燬帶來的時間成本
  • 避免無限制的線程創建導致資源耗盡
    :線程池限制了線程的數量,這樣就保證了不會因爲線程創建過多導致資源耗竭,程序崩潰的情況
線程池的應用場景

有大量的數據處理請求,需要多執行流併發/並行處理


線程池的實現

實現思路

首先線程池的核心,就是大量的線程和一個任務緩衝隊列。在線程池創建時就提前創建好一定數量的線程,如果有任務到來則將任務放入緩衝隊列中,此時喚醒線程池中的線程取出任務進行處理,如果線程池沒有空閒線程,則阻塞任務,直到有線程處理完任務迴歸線程池中。同時,因爲不同任務有不同的處理方法,所以要開放接口給外界,處理的數據和方法由外界自己定,線程池只負責調用對應的任務處理方法進行處理,不關心其中的內容。這樣就更具有靈活性。

代碼實現

下面就來實現一個線程池,具體的細節都有註釋

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

//類型重命名,將處理的方法定義爲一個函數指針
typedef void (*handler_t)(int);

const size_t MAX_SIZE = 10;

//任務
class ThreadTask
{
    public:
        //設置需要處理的數據與對應的處理方法
        void SetTask(int data, handler_t handler)
        {
            _data = data;
            _handler = handler;
        }

        //處理數據
        void Run()
        {
           _handler(_data);
        }

    private:
        int _data;
        handler_t _handler;
};

//線程池
class ThreadPool
{
    public:
        ThreadPool(size_t capacity = MAX_SIZE) : _capacity(capacity)
        {
            pthread_cond_init(&_cond, NULL);
            pthread_mutex_init(&_mutex, NULL);
            
            //線程池提前創建線程
            for(size_t i = 0; i < _capacity; i++)
            {
                pthread_t pid;
                int ret = pthread_create(&pid, NULL, start_routine, this);
                
                if(ret)
                {
                    std::cout << "線程創建失敗" << std::endl;
                    exit(-1);
                }
            }
        }
        
        ~ThreadPool()
        {
            pthread_cond_destroy(&_cond);
            pthread_mutex_destroy(&_mutex);
        }

        void Push(ThreadTask& Task)
        {
            //互斥鎖保證線程安全
            pthread_mutex_lock(&_mutex);
            //將任務放入隊列中
            _queue.push(Task);

            //喚醒線程池全部線程,誰搶到誰就來處理這個任務
            pthread_cond_broadcast(&_cond);
            pthread_mutex_unlock(&_mutex);
        }

        //入口函數的參數只能有一個void*,所以需要寫爲static函數來去掉隱含的this指針
        static void* start_routine(void *arg)
        {
            //將void*強轉爲需要的類型
            ThreadPool* pool = (ThreadPool*)arg;
            
            while(1)
            {
                pthread_mutex_lock(&pool->_mutex);   

				//如果任務隊列爲空則使線程循環等待
                while(pool->_queue.empty())
                {
                    pthread_cond_wait(&pool->_cond, &pool->_mutex);
                }

                ThreadTask Task;

                //將任務出隊進行處理
                Task = pool->_queue.front();
                pool->_queue.pop();

                pthread_mutex_unlock(&pool->_mutex);

                //解鎖後再處理,因爲加鎖只是保證隊列操作的安全性
                Task.Run();
            }

            
            return NULL;
        }

    private:
    	//互斥鎖保證線程安全,條件變量保證任務的提交與處理同步
        pthread_cond_t _cond;
        pthread_mutex_t _mutex;
        size_t _capacity;
        std::queue<ThreadTask> _queue;
};

下面寫一個函數來測試一下

#include "ThreadPool.hpp"
#include<unistd.h>

void handler1(int data)
{
    std::cout << "線程ID:"<< pthread_self() <<" 處理奇數數據:" << data << std::endl;

    sleep(1);
}

void handler2(int data)
{
    std::cout << "線程ID:"<< pthread_self() <<" 處理偶數數據:" << data << std::endl;

    sleep(1);
}

int main()
{
    ThreadPool pool;

    for(int i = 0; i < 10; i++)
    {
        ThreadTask task;
        
        //分別處理奇數和偶數
        if(i & 1)
        {
            task.SetTask(i, handler1);
        }
        else
        {
            task.SetTask(i, handler2);    
        }

        //任務放入隊列中
        pool.Push(task);
    }
    
    //休眠一段時間防止結束後主線程退出
    sleep(100);
    return 0;
}

使用兩種不同的處理方法來分別處理0-9的奇偶數

運行截圖
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章