線程池的概念? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
線程池是一種線程使用模式。?
一種線程使用模式。線程過多會帶來調度開銷,進而影響緩存局部性和整體性能。而線程池維護著多個線程,等待著監督管理者分配可并發執行的任務。
- 這避免了在處理短時間任務時創建與銷毀線程的代價。
- 線程池不僅能夠保證內核的充分利用,還能防止過分調度。
tips:可用線程數量應該取決于可用的并發處理器、處理器內核、內存、網絡sockets等的數量。
線程池的應用場景
- 需要大量的線程來完成任務,且完成任務的時間比較短。 WEB服務器完成網頁請求這樣的任務,使用線程池技術是非常合適的。因為單個任務小,而任務數量巨大,你可以想象一個熱門網站的點擊次數。 但對于長時間的任務,比如一個 Telnet連接請求,線程池的優點就不明顯了。因為Telnet會話時間比線程的創建時間大多了。
- 對性能要求苛刻的應用,比如要求服務器迅速響應客戶請求。
- 接受突發性的大量請求,但不至于使服務器因此產生大量線程的應用。突發性大量客戶請求,在沒有線程池情況下,將產生大量線程,雖然理論上大部分操作系統線程數目最大值不是問題,短時間內產生大量線程可能使內存到達極限,出現錯誤。
線程池的實現
下面我們實現一個簡單的線程池,線程池中提供了一個任務隊列,以及若干個線程(多線程)。
- 線程池中的多個線程負責從任務隊列當中拿任務,并將拿到的任務進行處理。
- 線程池對外提供一個Push接口,用于讓外部線程能夠將任務Push到任務隊列當中。
線程池代碼如下:
#include <iostream>
#include <pthread.h>
#include <vector>
#include <queue>
#include <string>static const int defaultnum = 5;struct ThreadInfo
{pthread_t tids;std::string NameThread;
};template<class T>
class ThreadPool
{
public:void Lock(){pthread_mutex_lock(&lock);}void UnLock(){pthread_mutex_unlock(&lock);}void Wait(){pthread_cond_wait(&cond, &lock);}void WakeUp(){pthread_cond_signal(&cond);}bool IsEmptyTask(){return _TasksQueue.size() == 0;}public:ThreadPool(int num = defaultnum): _threads(num){pthread_mutex_init(&lock, nullptr);pthread_cond_init(&cond, nullptr);}void ThreadStart(){int n = _threads.size();for(int i = 0; i < n; i++){pthread_create(&_threads[i].tids, nullptr, ThreadTasks, this);_threads[i].NameThread = "thread-" + std::to_string(i);}}std::string GetThreadName(pthread_t tid){for(auto& e : _threads){if(tid == e.tids){return e.NameThread; }}return "none";}static void *ThreadTasks(void* args){ThreadPool<T>* TP = static_cast<ThreadPool<T>*>(args);while(true){std::string Name = TP->GetThreadName(pthread_self());TP->Lock();while(TP->IsEmptyTask()){TP->Wait();}T t = TP->pop();TP->UnLock();t();std::cout << Name.c_str() << ' ' << std::endl;t.GetTask();}}T pop(){T t = _TasksQueue.front();_TasksQueue.pop();return t;}void push(const T &task){Lock();_TasksQueue.push(task);WakeUp();UnLock();}~ThreadPool(){pthread_mutex_destroy(&lock);pthread_cond_destroy(&cond);}
private:std::vector<ThreadInfo> _threads;std::queue<T> _TasksQueue;pthread_mutex_t lock;pthread_cond_t cond;
};
為什么線程池中需要有互斥鎖和條件變量?
使用互斥鎖的原因:STL容器一開始被設計時,就是為了追求效率,并沒有考慮線程安全,多線程場景,pop數據時可能產生并發問題
使用條件變量的原因:
為什么線程池中的線程執行例程需要設置為靜態方法?
Routine作為類的成員函數,該函數的第一個參數是隱藏的this指針,因此這里的Routine函數,雖然看起來只有一個參數,而實際上它有兩個參數,此時直接將該Routine函數作為創建線程時的執行例程是不行的,無法通過編譯。
靜態成員函數屬于類,而不屬于某個對象,也就是說靜態成員函數是沒有隱藏的this指針的,因此我們需要將Routine設置為靜態方法,此時Routine函數才真正只有一個參數類型為void*的參數。
但是在靜態成員函數內部無法調用非靜態成員函數,而我們需要在Routine函數當中調用該類的某些非靜態成員函數,比如Pop。因此我們需要在創建線程時,向Routine函數傳入的當前對象的this指針,此時我們就能夠通過該this指針在Routine函數內部調用非靜態成員函數了。
任務類型的設計
#pragma once#include "ThreadPool.hpp"enum
{EXITCODE = 0,DIVZERO,MODZERO
};class Task
{
public:Task(int x, int y, char oper, int exitcode_ = EXITCODE) : _data1(x), _data2(y), _oper(oper), exitcode(exitcode_){}void run(){switch (_oper){case '+':result = _data1 + _data2;break;case '-':result = _data1 - _data2;break;case '*':result = _data1 * _data2;break;case '/':if(_data1 == 0 | _data2 == 0){exitcode = DIVZERO;}else{result = _data1 / _data2;}break;case '%':if(_data1 == 0 | _data2 == 0){exitcode = MODZERO;}else{result = _data1 % _data2;}break;default:std::cout << "Symbol mismatch!" << std::endl;break;}SolveTask();}std::string _To_String(){std::string str;str += "[exitcode: ";str += std::to_string(exitcode);str += "]";str += " ";str += std::to_string(_data1);str += " ";str += _oper;str += " ";str += std::to_string(_data2);return str;}void GetTask(){std::cout << _data1 << " " << _oper << " " << _data2 << " = ?" << std::endl;}void SolveTask(){std::cout << _To_String() << " = " << result << std::endl;}void operator()(){run();}~Task(){}private:int _data1;int _data2;char _oper;int exitcode;int result;
};
主線程邏輯
主線程就負責不斷向任務隊列當中Push任務就行了,此后線程池當中的線程會從任務隊列當中獲取到這些任務并進行處理。
#include "SingletonThreadPool.hpp"
#include "Task.hpp"
#include <unistd.h>std::string oper = "+-*/%";int main()
{srand(time(nullptr));SingletonThreadPool<Task>* STP = new SingletonThreadPool<Task>(5);STP->ThreadStart();int len = oper.size(); while(true){sleep(1);int data1 = rand() % 10;int data2 = rand() % 10 + 1;char op = oper[rand() % len];Task t(data1, data2, op);t.push(t);t.GetTask();}return 0;
}
運行代碼后一瞬間就有六個線程,其中一個是主線程,另外五個是線程池內處理任務的線程。
注意:?此后我們如果想讓線程池處理其他不同的任務請求時,我們只需要提供一個任務類,在該任務類當中提供對應的任務處理方法就行了。
線程安全的單例模式
什么是單例模式
單例模式是一種 "經典的, 常用的, 常考的" 設計模式.
什么是設計模式
IT行業這么火, 涌入的人很多. 俗話說林子大了啥鳥都有. 大佬和菜雞們兩極分化的越來越嚴重. 為了讓菜雞們不太拖大佬的后腿, 于是大佬們針對一些經典的常見的場景, 給定了一些對應的解決方案, 這個就是 設計模式
單例模式的特點
某些類, 只應該具有一個對象(實例), 就稱之為單例.?在很多服務器開發場景中, 經常需要讓服務器加載很多的數據 (上百G) 到內存中. 此時往往要用一個單例的類來管理這些數據.
餓漢實現方式和懶漢實現方式
餓漢方式就是直接在類中將需要使用的對象先申請好
懶漢方式最核心的思想是 "延時加載". 從而能夠優化服務器的啟動速度.
例如:
#pragma onceclass Singleton
{
public:Singleton(){std::cout << "對象創建成功" << std::endl;}static Singleton& GetInstance(){return num;}private:static Singleton num;
};
#include <iostream>using namespace std;
#include "singleton.hpp"//static int num = 10;Singleton init1;
Singleton init2;
Singleton init3;
Singleton init4;
Singleton init5;
Singleton init6;int main()
{printf("啟動!!!\n"); return 0;
}
可以看到餓漢式的單例模式會在程序啟動之前對象就創建好了
餓漢方式實現單例模式
template <typename T> class Singleton
{static T data;
public:static T* GetInstance() {return &data;}
};
只要通過 Singleton 這個包裝類來使用 T 對象, 則一個進程中只有一個 T 對象的實例
懶漢方式實現單例模式
template <typename T> class Singleton
{static T* inst;
public:static T* GetInstance() {if(inst == NULL) {inst = new T();}return inst;}
};
存在一個嚴重的問題, 線程不安全.
第一次調用 GetInstance 的時候, 如果兩個線程同時調用, 可能會創建出兩份 T 對象的實例.
懶漢方式實現單例模式(線程安全版本)
// 懶漢模式, 線程安全
template <typename T>
class Singleton { volatile static T* inst; // 需要設置 volatile 關鍵字, 否則可能被編譯器優化. static std::mutex lock;
public: static T* GetInstance(){ if(inst == NULL) //避免占用CPU和操作系統資源,做無意義的事,提高效率{ lock.lock(); // 使用互斥鎖, 保證多線程情況下也只調用一次 new.if (inst == NULL) { inst = new T(); } lock.unlock(); } return inst; }
};
注意事項:
1. 加鎖解鎖的位置
2. 雙重 if 判定, 避免不必要的鎖競爭
3. volatile關鍵字防止過度優化
將上述線程池代碼改為單例模式
#pragma once#include <iostream>
#include <pthread.h>
#include <queue>
#include <vector>
#include <string>static const int defaultnum = 5;class ThreadInfo
{
public:pthread_t tid;std::string threadname;
};template <class T>
class SingletonThreadPool
{void Lock(){pthread_mutex_lock(&lock);}void UnLock(){pthread_mutex_unlock(&lock);}void Wait(){pthread_cond_wait(&cond, &lock);}void WakeUp(){pthread_cond_signal(&cond);}bool IsEmptyThreadPool(){return _tasksqueue.size() == 0;}public:static void* RoutineTasks(void* args){SingletonThreadPool<T> *TP = static_cast<SingletonThreadPool<T>*>(args);while(true){std::string name = TP->GetThreadName(pthread_self());TP->Lock();if(TP->IsEmptyThreadPool()){TP->Wait();}T t = TP->pop();TP->UnLock();t();std::cout << name << ' ' << std::endl;t.GetTask();}}public:void ThreadStart(){int num = _threads.size();for(int i = 0; i < num; i++){pthread_create(&_threads[i].tid, nullptr, RoutineTasks, this);_threads[i].threadname = "thread-" + std::to_string(i);}}T pop(){T task = _tasksqueue.front();_tasksqueue.pop();return task;}std::string GetThreadName(pthread_t tid){for(const auto& e : _threads){if(tid == e.tid)return e.threadname;}return "none";}void push(const T& task){Lock();_tasksqueue.push(task);WakeUp();UnLock();}static SingletonThreadPool<T>* GetInStance(){//避免占用CPU和操作系統資源,做無意義的事,提高效率if(inst == nullptr){//避免多線程模式下,同時申請多個instpthread_mutex_lock(&slock);if(inst == nullptr){inst = new SingletonThreadPool<T>;}pthread_mutex_unlock(&slock);}return inst;}private:SingletonThreadPool(int num = defaultnum):_threads(num){pthread_mutex_init(&lock, nullptr);pthread_cond_init(&cond, nullptr);}~SingletonThreadPool(){pthread_mutex_destroy(&lock);pthread_cond_destroy(&cond);}//防拷貝SingletonThreadPool(const SingletonThreadPool<T>& STP) = delete;SingletonThreadPool<T>& operator=(const SingletonThreadPool<T>& STP) = delete;private:std::vector<ThreadInfo> _threads;std::queue<T> _tasksqueue;pthread_mutex_t lock;pthread_cond_t cond;static SingletonThreadPool<T> *inst;static pthread_mutex_t slock;
}; template<class T>
SingletonThreadPool<T>* SingletonThreadPool<T>::inst = nullptr;template<class T>
pthread_mutex_t SingletonThreadPool<T>::slock = PTHREAD_MUTEX_INITIALIZER;
#include "SingletonThreadPool.hpp"
#include "Task.hpp"
#include <unistd.h>std::string oper = "+-*/%";int main()
{srand(time(nullptr));SingletonThreadPool<Task>::GetInStance()->ThreadStart();int len = oper.size(); while(true){sleep(1);int data1 = rand() % 10;int data2 = rand() % 10 + 1;char op = oper[rand() % len];Task t(data1, data2, op);SingletonThreadPool<Task>::GetInStance()->push(t);t.GetTask();}return 0;
}