在新頒布的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++書