簡單的線程池

簡單的線程池

什麼是線程池?

線程池就是線程的一種使用模式。雖然線程是輕量級的進程,但是線程的創建和銷燬還是會引發效率問題。並且,如果創建的線程過多,反而會增加很多的調度開銷,影響系統效率。

線程池就是可以提前創建好一些線程,在我們需要使用線程的時候,對已經創建好的線程添加任務就好。

線程池的使用場景:

  • 需要大量的線程來完成任務,且完成任務的時間比較短。 WEB服務器完成網頁請求這樣的任務,使用線程池技術是非常合適的。因爲單個任務小,而任務數量巨大,你可以想象一個熱門網站的點擊次數。 但對於長時間的任務,比如一個Telnet連接請求,線程池的優點就不明顯了。因爲Telnet會話時間比線程的創建時間大多了。

  • 對性能要求苛刻的應用,比如要求服務器迅速響應客戶請求。

  • 接受突發性的大量請求,但不至於使服務器因此產生大量線程的應用。突發性大量客戶請求,在沒有線程池情況下,將產生大量線程,雖然理論上大部分操作系統線程數目最大值不是問題,短時間內產生大量線程可能使內存到達極限,出現錯誤。

線程池原理:

這裏我寫的這個線程池的原理如下:

  • 創建固定數量的線程,循環從任務隊列(阻塞隊列)中獲取任務對象。
  • 獲取到任務對象後,執行任務對象中任務接口。

Code

阻塞隊列:

#pragma once
#include <iostream>
#include <queue>
#include <vector>
#include <unistd.h>


// 用信號量和環形數組來實現阻塞隊列
// 信號量表示可用資源個數
// 一個信號量表示當前隊列中元素的個數
// 另一個信號量表示當前隊列中空格的個數
// 插入元素就是在消耗一個空格資源,釋放了一個元素資源
// 刪除元素就是在消耗一個元素資源,釋放了一個空格資源
#include <semaphore.h>

template <class T>
class BlockingQueue
{
public:
    BlockingQueue(int capacity)
        :_head(0),
        _tail(0),
        _size(0),
        _capicity(capacity)
    {
        _qu.resize(capacity);
        sem_init(&_lock, 0, 1);
        sem_init(&_elem, 0, 0);
        sem_init(&_blank, 0, _capicity);
    }

    virtual ~BlockingQueue()
    {
        sem_destroy(&_lock);
        sem_destroy(&_elem);
        sem_destroy(&_blank);
    }

    void Push(const T& data)
    {
        // 每次插入元素前,申請一個空格資源
        // 如果沒有空格資源,說明隊列滿了
        // 滿了就不能繼續插入,並且在 Push 中阻塞
        sem_wait(&_blank);

        sem_wait(&_lock);
        _qu[_tail++] = data;
        _tail %= _capicity;
        _size++;
        sem_post(&_lock);

        sem_post(&_elem);
    }

    void Pop(T* data)
    {
        // 每次出隊列前要申請一個元素資源
        // 如果沒有元素資源,隊列爲空
        // 不能出隊列,要在Pop 中阻塞
        sem_wait(&_elem);

        sem_wait(&_lock);
        *data = _qu[_head++];
        _head %= _capicity;
        _size--;
        sem_post(&_lock);

        sem_post(&_blank);
    }

private:
    sem_t _lock;
    std::vector<T> _qu;
    int _head;
    int _tail;
    int _size;
    int _capicity;
    sem_t _elem;
    sem_t _blank;
};

線程池

#pragma once
#include <iostream>
#include "BlockingQueue.hpp"

class Task
{
public:
    virtual void Run()
    {
        std::cout << "Base Run" << std::endl;
    }

    virtual ~Task()
    {
    }
};

// 線程池對象啓動的時候會創建一組線程
// 每個線程都會完成一定的任務(執行一定的代碼邏輯, 這個邏輯是由調用者來決定)
//
// 任務是一段代碼,就可以用函數來表示
class ThreadPool
{
public:
    ThreadPool(int n) 
        :qu_(100),
        woker_(n)
    {
        // 創建出若干個線程
        for (int i = 0; i < n; i++)
        {
            pthread_t tid;
            pthread_create(&tid, nullptr, ThreadEntry, this);
            wokers_.push_back(tid);
        }
    }
    ~ThreadPool()
    {
        // 讓線程退出,然後再回收
        for (size_t i = 0; i < wokers_.size(); i++)
        {
            pthread_cancel(wokers_[i]);
        }
        for (size_t i = 0; i < wokers_.size(); i++)
        {
            pthread_join(wokers_[i], nullptr);
        }
    }

    // 使用線程池的時候就需要由調用者加入一些任務
    // 讓線程去執行
    void AddTask(Task *task)
    {
        qu_.Push(task);
    }
private:
    BlockingQueue<Task*> qu_;
    int woker_;
    std::vector<pthread_t> wokers_;

private:
    static void *ThreadEntry(void *arg)
    {
        ThreadPool *pool = (ThreadPool *)arg;

        while (true)
        {
            // 循環中嘗試從阻塞隊列中獲取到一個任務
            // 就執行一個任務
            Task *task = nullptr;
            pool->qu_.Pop(&task);

            task->Run();
            delete task;
        }

        return nullptr;
    }
};
  • 在創建對象的時候,會創建出若干線程,並且線程會從阻塞隊列中獲取任務。
  • 這裏有一個細節就是,如果阻塞隊列爲空,線程的創建也就會阻塞。所以在 Task 類中,Run函數可以定義爲純虛函數。
  • 因爲任務是 new 出來的,所以線程執行完任務需要將其 delete。

Main.cpp

#include "ThreadPool.hpp"


// 這個類由用戶自己定製
// 需要依賴哪些數據,都可以隨意添加和修改
class MyTask : public Task
{
public:
    MyTask(int id)
        :id_(id)
    {

    }
    virtual void Run()
    {
        // 執行用戶自定製的邏輯
        std::cout << "id = " << id_ << std::endl;
    }

private:
    int id_;
};

int main()
{
    ThreadPool pool(10);
    
    for (int i = 0; i < 20; i++)
    {
        pool.AddTask(new MyTask(i));
    }

    while (1)
    {
        sleep(1);
    }
    return 0;
}

這是一個非常基礎的線程池,結構也比較簡單,也比較好懂。

叮~🔔

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