簡單的線程池
什麼是線程池?
線程池就是線程的一種使用模式。雖然線程是輕量級的進程,但是線程的創建和銷燬還是會引發效率問題。並且,如果創建的線程過多,反而會增加很多的調度開銷,影響系統效率。
線程池就是可以提前創建好一些線程,在我們需要使用線程的時候,對已經創建好的線程添加任務就好。
線程池的使用場景:
-
需要大量的線程來完成任務,且完成任務的時間比較短。 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;
}
這是一個非常基礎的線程池,結構也比較簡單,也比較好懂。
叮~🔔