C++實現簡單線程池

一些C++11特性

了解一下lambda表達式,利用Lambda表達式,可以方便的定義和創建匿名函數

值捕獲

int main()
{
    int a = 123;
    auto f = [a] { cout << a << endl; }; 
    a = 321;
    f(); // 輸出:123
}

引用捕獲

int main()
{
    int a = 123;
    auto f = [&a] { cout << a << endl; }; 
    a = 321;
    f(); // 輸出:321
}

隱式捕獲

int main()
{
    int a = 123;
    auto f = [=] { cout << a << endl; };    // 值捕獲
    f(); // 輸出:123
}

int main()
{
    int a = 123;
    auto f = [&] { cout << a << endl; };    // 引用捕獲
    a = 321;
    f(); // 輸出:321
}

類型尾置
讓編譯器在函數定義的時候知道返回類型

template <typename T>
auto &getItem(T begin, T end) -> decltype(*begin) {
    return *begin; // 返回序列中一個元素的引用
}

線程相關操作

#include <iostream>
#include <thread>
void foo() {
    std::cout << "hello world" << std::endl;
}
int main() {
    std::thread t(foo);//創建一個線程實例
    t.join();//加入一個線程
    return 0;
}


std::mutex mutex  創建一個互斥量
std::lock_guard<std::mutex> lock(mutex); 對互斥量上鎖
std::unique_lock   也是上鎖,但更靈活
std::packaged_task<int()> task([](){return 7;});  用來封裝任何可以調用的目標,從而用于實現異步的調用,異步即主線程A想獲取某個計算結果而調用線程B
std::future<int> result = task.get_future();   用來獲取異步任務的結果
std::thread(std::move(task)).detach();   一個線程中執行 task
std::this_thread::sleep_for   當前線程休眠一段時間,休眠期間不與其他線程競爭CPU,根據線程需求,等待若干時間
std::condition_variable   喚醒等待線程從而避免死鎖
std::bind  將實參綁定到調用函數上
std::placeholders::_1  占用符
std::shared_ptr   一種智能指針,它能夠記錄多少個 shared_ptr 共同指向一個對象
std::make_shared   分配創建傳入參數中的對象,并返回這個對象類型的std::shared_ptr指針
std::move  將自己的參數轉換為右值
std::forward 會把參數被綁定到一個右值的時候將其轉化為右值
std::result_of 在編譯的時候推導出一個函數調用表達式的返回值類型

看一個操作系統中生產者與消費者問題

假設存在一個緩沖區,生產者往里面存數據,消費者從里面取數據,如果緩沖區滿了,生產者就不能再往里面添加數據。如果緩沖區沒有數據,消費者不能從里面取

下面利用c++11來寫一個簡單的模型,要理解多線程并發,一個程序可能由多個線程來執行,因此程序上的順序并不同于多線程中的執行順序,代碼中先寫5個生產者,再寫5個消費者,但是線程中的順序并不是這樣,可能先執行一個生產者,再執行一個消費者。

#include <condition_variable>
#include <mutex>
#include <thread>
#include <iostream>
#include <queue>
#include <chrono>
int main()
{
    // 生產者數量
    std::queue<int> produced_nums;
    // 互斥鎖
    std::mutex m;
    // 條件變量
    std::condition_variable cond_var;
    // 結束標志
    bool done = false;
    // 通知標志
    bool notified = false;

    // 生產者線程
    std::thread producer([&]() {
        for (int i = 0; i < 5; ++i) {
            std::this_thread::sleep_for(std::chrono::seconds(1));//當前線程休眠
            // 創建互斥鎖
            std::unique_lock<std::mutex> lock(m);
            std::cout << "producing " << i << '\n';
            produced_nums.push(i);
            notified = true;
            // 通知一個線程
            cond_var.notify_one();
        }
        done = true;//生產結束
        cond_var.notify_one();//通知休眠的線程執行,完成所有的進程
    });

    // 消費者線程
    std::thread consumer([&]() {
        std::unique_lock<std::mutex> lock(m);
        while (!done) {
            while (!notified) {  // 循環避免虛假喚醒,執行完生產者的進程,notified為true,否則停掉消費者線程
                cond_var.wait(lock);//停掉當前線程

            }
            while (!produced_nums.empty()) {
                std::cout << "consuming " << produced_nums.front() << '\n';
                produced_nums.pop();
            }
            notified = false;
        }
    });
    producer.join();
    consumer.join();
}
#ifndef ThreadPool_hpp
#define ThreadPool_hpp
#include <vector>               // std::vector
#include <queue>                // std::queue
#include <memory>               // std::make_shared
#include <stdexcept>            // std::runtime_error
#include <thread>               // std::thread
#include <mutex>                // std::mutex,        std::unique_lock
#include <condition_variable>   // std::condition_variable
#include <future>               // std::future,       std::packaged_task
#include <functional>           // std::function,     std::bind
#include <utility>              // std::move,         std::forward

class ThreadPool {
public:
    inline ThreadPool(size_t threads) : stop(false) { //構造函數,且把stop變量賦值為false
        for(size_t i = 0;i<threads;++i)//創造線程實例
            workers.emplace_back([this] {//使用lambda表達式返回this
                for(;;)
                {
                    std::function<void()> task;//function函數對象類,可調用實體的一種類型安全的包裹
                    {
                        std::unique_lock<std::mutex> lock(this->queue_mutex);//互斥量上鎖
                        //std::cout<<"thread"<<std::this_thread::get_id()<<"begin work"<<std::endl;
                        this->condition.wait(lock,[this]{ return this->stop || !this->tasks.empty(); });//如果線程池沒有銷毀且任務隊列為空,返回false,該線程休眠
                        if(this->stop && this->tasks.empty())//線程池銷毀且任務隊列為空,返回
                            return;
                        task = std::move(this->tasks.front());
                        this->tasks.pop();

                    }
                    task();//執行任務
                }

                std::cout<<"thread"<<std::this_thread::get_id()<<"begin work"<<std::endl;
            });


    }
    inline ~ThreadPool() {
        {
            //std::unique_lock<std::mutex> lock(queue_mutex);//互斥量上鎖,避免
            stop = true;
        }
        condition.notify_all();//通知所有的休眠線程
        for(std::thread &worker: workers)
            worker.join();
    }
    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args)//可變參數的模板,Args 是一個模板參數包。而在后面的函數參數表中,args 則是函數參數包,用來表示零個或多個參數。
    -> std::future<typename std::result_of<F(Args...)>::type> {
        using return_type = typename std::result_of<F(Args...)>::type;//獲取函數返回類型
        auto task = std::make_shared< std::packaged_task<return_type()>>(
                std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
        std::future<return_type> res = task->get_future();//獲得 std::future 對象以供實施線程同步
        {
            std::unique_lock<std::mutex> lock(queue_mutex);

            if(stop)
                throw std::runtime_error("enqueue on stopped ThreadPool");
            tasks.emplace([task]{ (*task)(); });//把任務加入隊列
        }
        condition.notify_one();//喚醒一個休眠的線程
        return res;
    }
private:
    std::vector<std::thread> workers;//線程池
    std::queue<std::function<void()>> tasks;//任務隊列
    std::mutex queue_mutex;//互斥量
    std::condition_variable condition;//條件變量
    bool stop;//結束標志
};
#endif /* ThreadPool_hpp */

偽代碼:

semaphore mutex=1; //臨界區互斥信號量
semaphore empty=n;  //空閑緩沖區
semaphore full=0;  //緩沖區初始化為空
producer ()//生產者進程 
{
    while(1)
    {
        produce an item in nextp;  //生產數據
        P(empty);  //獲取空緩沖區單元
        P(mutex);  //進入臨界區.
        add nextp to buffer;  //將數據放入緩沖區
        V(mutex);  //離開臨界區,釋放互斥信號量
        V(full);  //滿緩沖區數加1
    }
}

consumer ()//消費者進程
{
    while(1)
    {
        P(full);  //獲取滿緩沖區單元
        P(mutex);  // 進入臨界區
        remove an item from buffer;  //從緩沖區中取出數據
        V (mutex);  //離開臨界區,釋放互斥信號量
        V (empty) ;  //空緩沖區數加1
        consume the item;  //消費數據
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373