三河市建設(shè)廳公示網(wǎng)站百度的seo關(guān)鍵詞優(yōu)化怎么弄
🎇Linux:
- 博客主頁:一起去看日落嗎
- 分享博主的在Linux中學習到的知識和遇到的問題
博主的能力有限,出現(xiàn)錯誤希望大家不吝賜教
- 分享給大家一句我很喜歡的話: 看似不起波瀾的日復一日,一定會在某一天讓你看見堅持的意義,祝我們都能在雞零狗碎里找到閃閃的快樂🌿🌞🐾。
🍁 🍃 🍂 🌿
目錄
- 🌿1. Linux線程池
- 🍃1.1 線程池的概念
- 🍃1.2 線程池的優(yōu)點
- 🍃1.3 線程池的應(yīng)用場景
- 🍃1.4 線程池的實現(xiàn)
🌿1. Linux線程池
🍃1.1 線程池的概念
線程池是一種線程使用模式。
線程過多會帶來調(diào)度開銷,進而影響緩存局部和整體性能,而線程池維護著多個線程,等待著監(jiān)督管理者分配可并發(fā)執(zhí)行的任務(wù)。
🍃1.2 線程池的優(yōu)點
- 線程池避免了在處理短時間任務(wù)時創(chuàng)建與銷毀線程的代價。
- 線程池不僅能夠保證內(nèi)核充分利用,還能防止過分調(diào)度。
注意: 線程池中可用線程的數(shù)量應(yīng)該取決于可用的并發(fā)處理器、處理器內(nèi)核、內(nèi)存、網(wǎng)絡(luò)sockets等的數(shù)量。
🍃1.3 線程池的應(yīng)用場景
線程池常見的應(yīng)用場景如下:
- 需要大量的線程來完成任務(wù),且完成任務(wù)的時間比較短。
- 對性能要求苛刻的應(yīng)用,比如要求服務(wù)器迅速響應(yīng)客戶請求。
- 接受突發(fā)性的大量請求,但不至于使服務(wù)器因此產(chǎn)生大量線程的應(yīng)用。
相關(guān)解釋:
- 像Web服務(wù)器完成網(wǎng)頁請求這樣的任務(wù),使用線程池技術(shù)是非常合適的。因為單個任務(wù)小,而任務(wù)數(shù)量巨大,你可以想象一個熱門網(wǎng)站的點擊次數(shù)。
- 對于長時間的任務(wù),比如Telnet連接請求,線程池的優(yōu)點就不明顯了。因為Telnet會話時間比線程的創(chuàng)建時間大多了。
- 突發(fā)性大量客戶請求,在沒有線程池的情況下,將產(chǎn)生大量線程,雖然理論上大部分操作系統(tǒng)線程數(shù)目最大值不是問題,但短時間內(nèi)產(chǎn)生大量線程可能使內(nèi)存到達極限,出現(xiàn)錯誤。
🍃1.4 線程池的實現(xiàn)
下面我們實現(xiàn)一個簡單的線程池,線程池中提供了一個任務(wù)隊列,以及若干個線程(多線程)。
- 線程池中的多個線程負責從任務(wù)隊列當中拿任務(wù),并將拿到的任務(wù)進行處理。
- 線程池對外提供一個Push接口,用于讓外部線程能夠?qū)⑷蝿?wù)Push到任務(wù)隊列當中。
#pragma once#include <iostream>
#include <unistd.h>
#include <queue>
#include <pthread.h>#define NUM 5//線程池
template<class T>
class ThreadPool
{
private:bool IsEmpty(){return _task_queue.size() == 0;}void LockQueue(){pthread_mutex_lock(&_mutex);}void UnLockQueue(){pthread_mutex_unlock(&_mutex);}void Wait(){pthread_cond_wait(&_cond, &_mutex);}void WakeUp(){pthread_cond_signal(&_cond);}
public:ThreadPool(int num = NUM): _thread_num(num){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}//線程池中線程的執(zhí)行例程static void* Routine(void* arg){pthread_detach(pthread_self());ThreadPool* self = (ThreadPool*)arg;//不斷從任務(wù)隊列獲取任務(wù)進行處理while (true){self->LockQueue();while (self->IsEmpty()){self->Wait();}T task;self->Pop(task);self->UnLockQueue();task.Run(); //處理任務(wù)}}void ThreadPoolInit(){pthread_t tid;for (int i = 0; i < _thread_num; i++){pthread_create(&tid, nullptr, Routine, this); //注意參數(shù)傳入this指針}}//往任務(wù)隊列塞任務(wù)(主線程調(diào)用)void Push(const T& task){LockQueue();_task_queue.push(task);UnLockQueue();WakeUp();}//從任務(wù)隊列獲取任務(wù)(線程池中的線程調(diào)用)void Pop(T& task){task = _task_queue.front();_task_queue.pop();}
private:std::queue<T> _task_queue; //任務(wù)隊列int _thread_num; //線程池中線程的數(shù)量pthread_mutex_t _mutex;pthread_cond_t _cond;
};
為什么線程池中需要有互斥鎖和條件變量?
線程池中的任務(wù)隊列是會被多個執(zhí)行流同時訪問的臨界資源,因此我們需要引入互斥鎖對任務(wù)隊列進行保護。
線程池當中的線程要從任務(wù)隊列里拿任務(wù),前提條件是任務(wù)隊列中必須要有任務(wù),因此線程池當中的線程在拿任務(wù)之前,需要先判斷任務(wù)隊列當中是否有任務(wù),若此時任務(wù)隊列為空,那么該線程應(yīng)該進行等待,直到任務(wù)隊列中有任務(wù)時再將其喚醒,因此我們需要引入條件變量。
當外部線程向任務(wù)隊列中Push一個任務(wù)后,此時可能有線程正處于等待狀態(tài),因此在新增任務(wù)后需要喚醒在條件變量下等待的線程。
注意:
- 當某線程被喚醒時,其可能是被異常或是偽喚醒,或者是一些廣播類的喚醒線程操作而導致所有線程被喚醒,使得在被喚醒的若干線程中,只有個別線程能拿到任務(wù)。此時應(yīng)該讓被喚醒的線程再次判斷是否滿足被喚醒條件,所以在判斷任務(wù)隊列是否為空時,應(yīng)該使用while進行判斷,而不是if。
- pthread_cond_broadcast函數(shù)的作用是喚醒條件變量下的所有線程,而外部可能只Push了一個任務(wù),我們卻把全部在等待的線程都喚醒了,此時這些線程就都會去任務(wù)隊列獲取任務(wù),但最終只有一個線程能得到任務(wù)。一瞬間喚醒大量的線程可能會導致系統(tǒng)震蕩,這叫做驚群效應(yīng)。因此在喚醒線程時最好使用pthread_cond_signal函數(shù)喚醒正在等待的一個線程即可。
- 當線程從任務(wù)隊列中拿到任務(wù)后,該任務(wù)就已經(jīng)屬于當前線程了,與其他線程已經(jīng)沒有關(guān)系了,因此應(yīng)該在解鎖之后再進行處理任務(wù),而不是在解鎖之前進行。因為處理任務(wù)的過程可能會耗費一定的時間,所以我們不要將其放到臨界區(qū)當中。
- 如果將處理任務(wù)的過程放到臨界區(qū)當中,那么當某一線程從任務(wù)隊列拿到任務(wù)后,其他線程還需要等待該線程將任務(wù)處理完后,才有機會進入臨界區(qū)。此時雖然是線程池,但最終我們可能并沒有讓多線程并行的執(zhí)行起來。
為什么線程池中的線程執(zhí)行例程需要設(shè)置為靜態(tài)方法?
使用pthread_create函數(shù)創(chuàng)建線程時,需要為創(chuàng)建的線程傳入一個Routine(執(zhí)行例程),該Routine只有一個參數(shù)類型為void的參數(shù),以及返回類型為void的返回值。
而此時Routine作為類的成員函數(shù),該函數(shù)的第一個參數(shù)是隱藏的this指針,因此這里的Routine函數(shù),雖然看起來只有一個參數(shù),而實際上它有兩個參數(shù),此時直接將該Routine函數(shù)作為創(chuàng)建線程時的執(zhí)行例程是不行的,無法通過編譯。
靜態(tài)成員函數(shù)屬于類,而不屬于某個對象,也就是說靜態(tài)成員函數(shù)是沒有隱藏的this指針的,因此我們需要將Routine設(shè)置為靜態(tài)方法,此時Routine函數(shù)才真正只有一個參數(shù)類型為void*的參數(shù)。
但是在靜態(tài)成員函數(shù)內(nèi)部無法調(diào)用非靜態(tài)成員函數(shù),而我們需要在Routine函數(shù)當中調(diào)用該類的某些非靜態(tài)成員函數(shù),比如Pop。因此我們需要在創(chuàng)建線程時,向Routine函數(shù)傳入的當前對象的this指針,此時我們就能夠通過該this指針在Routine函數(shù)內(nèi)部調(diào)用非靜態(tài)成員函數(shù)了。
任務(wù)類型的設(shè)計
我們將線程池進行了模板化,因此線程池當中存儲的任務(wù)類型可以是任意的,但無論該任務(wù)是什么類型的,在該任務(wù)類當中都必須包含一個Run方法,當我們處理該類型的任務(wù)時只需調(diào)用該Run方法即可。
下面我們實現(xiàn)一個計算任務(wù)類:
#pragma once#include <iostream>//任務(wù)類
class Task
{
public:Task(int x = 0, int y = 0, char op = 0): _x(x), _y(y), _op(op){}~Task(){}//處理任務(wù)的方法void Run(){int result = 0;switch (_op){case '+':result = _x + _y;break;case '-':result = _x - _y;break;case '*':result = _x * _y;break;case '/':if (_y == 0){std::cerr << "Error: div zero!" << std::endl;return;}else{result = _x / _y;}break;case '%':if (_y == 0){std::cerr << "Error: mod zero!" << std::endl;return;}else{result = _x % _y;}break;default:std::cerr << "operation error!" << std::endl;return;}std::cout << "thread[" << pthread_self() << "]:" << _x << _op << _y << "=" << result << std::endl;}
private:int _x;int _y;char _op;
};
此時線程池內(nèi)的線程不斷從任務(wù)隊列拿出任務(wù)進行處理,而它們并不需要關(guān)心這些任務(wù)是哪來的,它們只需要拿到任務(wù)后執(zhí)行對應(yīng)的Run方法即可。
主線程邏輯
主線程就負責不斷向任務(wù)隊列當中Push任務(wù)就行了,此后線程池當中的線程會從任務(wù)隊列當中獲取到這些任務(wù)并進行處理。
#include "Task.hpp"
#include "ThreadPool.hpp"int main()
{srand((unsigned int)time(nullptr));ThreadPool<Task>* tp = new ThreadPool<Task>; //線程池tp->ThreadPoolInit(); //初始化線程池當中的線程const char* op = "+-*/%";//不斷往任務(wù)隊列塞計算任務(wù)while (true){sleep(1);int x = rand() % 100;int y = rand() % 100;int index = rand() % 5;Task task(x, y, op[index]);tp->Push(task);}return 0;
}