程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++小品:她來聽我的演唱會——C++11中的隨機數、線程(thread)、互斥(mutex)和條件變

C++小品:她來聽我的演唱會——C++11中的隨機數、線程(thread)、互斥(mutex)和條件變

編輯:C++入門知識

 

在新頒布的C++新標准C++11中,最令人激動人心的,我想不是auto關鍵字,也不是Lambda表達式,而是其中的對並行計算的支持——新的線程庫(thread)的加入。

 

多核心CPU的普及應用,C++的主要應用領域,服務器程序,高性能計算等等,都對並行計算提出了新的要求,而這次C++中全新添加的線程庫,就是對這一趨勢的應對。現在,C++程序員可以輕松地編寫多線程的程序,而無需借助系統API或者是第三方程序庫的支持。線程庫的加入給C++帶來的變化,無異於194,翻身的程序員們把歌唱。

 

C++11中的線程庫,很大程度上直接來自boost這塊C++的試驗田,其基本架構和組件都完全相同,如果你是一個boost線程庫的使用者,那麼在C++11中,你會感覺到是回到了老家一樣,到處都是熟人。而如果你是一個完全的新手,也不要緊,C++11中的線程庫非常簡單,任何人都可以輕松上手,我就是這樣,但是要深究,還得好好學習。

 

下面是一個簡單的例子,用到了線程庫中的線程(thread),互斥(mutex),條件變量(condition),來模擬一個演唱會的入場檢票的場景,另外,為了模擬觀眾,用到了C++11中的新的隨機數的產生,模擬一個正態分布的訪客人群。不說了,還是看代碼:

 

#include <iostream>

#include <queue>

#include <vector>

// 隨機數

#include <random>

// 這裡,我使用了boost實現的線程庫,如果你的編譯器已經支持C++11,則使用<thread>是一樣的

#include <boost\thread.hpp>

#include <boost\thread\locks.hpp>

#include <boost\thread\condition.hpp>

 

using namespace std;

using namespace boost;

 

// 共享資源和互斥對象

mutex mtx;

bool finish = false;  // 表示觀眾到來是否結束

 

// 觀眾,主要是為了表示檢票過程中的檢票耗費時間

class viewer

{

public:

    void check()

    {

        // 線程等待

        posix_time::milliseconds worktime(400); 

        this_thread::sleep(worktime);   

    }

    void arrival(int t)

    {

        posix_time::seconds arrtime(t); 

        this_thread::sleep(arrtime);   

    }

};

// 檢票口

// 它有一個隊列,用於保存到來的觀眾,並且用一個線程來處理隊列中的觀眾

class gate

{

    typedef boost::mutex::scoped_lock scoped_lock;

public:

    gate():count(0),no(0){};

    // 啟動線程

    void start(int n)

    {

        no = n;

        t = thread(&gate::check,this);

    }

 

    // 檢票

    void check()

    {

        // 無限循環,知道觀眾數為0且不會有新的觀眾到來

        while(true)

        {

            viewer v;

            {

                // 鎖定互斥對象,開始訪問對列

                scoped_lock lock(m);

                if(0==vque.size())  // 如果隊列為空

                {

                    {

                    // 判斷是否還會有新的觀眾到來,也即是表示到達的線程是否結束

                    scoped_lock finlk(mtx);

                    if(finish)

                        return; // 如果已經結束,檢票也同樣結束

                    }

                    // 如果觀眾數為0,則等待新的觀眾的到來

                    while(0 == vque.size())

                    {   

                          // 這裡的wait()是條件變量的關鍵,它會先是否lock所鎖定的互斥對象m一定時間,

                          // 然後再次鎖定,接著進行(0==vque.size())的判斷。如此往復,知道size不等於0,

                          // 循環條件無法滿足而結束循環,這裡表達的條件就是,只有size!=0,也就是隊列中有

                          // 觀眾才繼續向下。

                          cond.wait(lock);

                    }

                }

                // 從對列中獲得觀眾,對其進行檢票

                v = vque.front();

                vque.pop();

                cond.notify_one(); // 這裡是通知添加觀眾的進程,表示隊列已經有空位置了,可以添加新的觀眾

            }

            v.check();

            ++count;

        }

    }

    // 將觀眾添加到隊列

    void add(viewer v)

    {

 

        // 同樣運用條件變量,判斷隊列是否已經滿了

        // 只有在隊列尚未滿的情況下才向下繼續

        scoped_lock lock(m);

        while(vque.size() >= 15 )

        {

            cond.wait(lock);

        }

        vque.push(v); // 將觀眾添加到隊列

        cond.notify_one();  // 通知檢票進程,新的觀眾進入隊列,這樣在size=0時等待的條件可以更新

    }

    int getcount()

    {

        return count;

    }

    int getno()

    {

        return no;

    }

    // 等待線程執行完畢返回

    void join()

    {

        t.join();

    }

private:

    thread t;

    mutex m;

    condition cond;

    queue<viewer> vque;

    int count;

    int no;

};

 

// 一共有10個檢票口

vector<gate> vgates(10);

 

// 用隨機數模擬觀眾到達

void arrival()

{   

    default_random_engine re{}; // 產生一個均值為31的正態分布的隨機數

    normal_distribution<double> nd(31,8);

 

    // 將隨機數引擎和分布綁定一個函數對象

    auto norm = std::bind(nd, re);

    // 保存隨機數的容器

    vector<int> mn(64);

   

    // 產生隨機數

    for(int i = 0;i<700;++i)

        ++mn[round(norm())];

   

    int secs = 100;

    // 產生0到9的隨機數,表示觀眾隨機地到達某一個檢票口

    uniform_int_distribution<int>  index{0,9};

    

    // 進入檢票口隊列

    for(auto i:mn)

    {

        cout<<i<<endl;

        for(auto vi = 1; vi <= i; ++vi)

        {

            // 將觀眾添加到某個gate的隊列中

             (vgates[index(re)]).add(viewer());

            // 等待一段時間

            int t = round(secs/(float)(i+1));

            this_thread::sleep(

            posix_time::milliseconds(t));

        }

    }

    // 觀眾已經全部到達,進入隊列

     cout<<"finish"<<endl;

    mtx.lock();

    finish = true;

    mtx.unlock();

    //cout<<"unlock"<<endl;

}

 

int main()

{

    int i = 1;

    // 啟動檢票線程

    for(gate& g:vgates)

    {

        g.start(i);

        ++i;

    }

    // 啟動到達線程,看看,在C++11中新線程的創建就這麼簡單

    thread arr = thread(arrival);

    // 等待線程結束

    arr.join();

    int total = 0;

    // 等待檢票線程結束,並輸出處理的人數

    for(gate& g:vgates)

    {

        g.join();

        total += g.getcount();

        cout<<"gate "<<g.getno()

            <<" processed "<<g.getcount()<<" viewers."<<endl;

    }

    cout<<"there are "<<total<<"viewers in total."<<endl;

    return 0;

}

這就是一個線程庫的簡單應用,模擬了非常復雜的場景。

 

因為自己對多線程開發還不太熟悉,這個程序在某些特定條件下會產生了死鎖,還有待進一步完善  

摘自 我的第一本C++書

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved