C++非同步程式設計最佳實踐

娃娃魚2發表於2016-08-03

Mapreduce問題

多個資料,進行同型別計算,最後彙總結果,怎樣用C++解鎖此類問題?

這個最簡單了,單執行緒迴圈處理每份資料好了。
這有何難,建立一塊地方,針對每份資料建立個執行緒執行計算,將結果寫入先前建立的資料的對應地方,等各執行緒結束,完活。

上面的說法都對,只是不夠好。
對於單執行緒處理,在這個多核時代,未免大馬拉小車,有點浪費CPU。當問題規模變大,你準備花多長時間計算。
對於多執行緒這個說法,從策略上來說,沒有問題。但問題是你不得不手動啟動執行緒,把結果放到共享記憶體裡,通過thread.join()等待結束。這點操作難度,對於我大C++開發來說算啥,分分鐘就搞定了。但問題又來了,現在你的老闆問你要針對單份資料的處理函式,返回結果。
呵呵,原來的單份資料處理函式,十有八九長成這樣:

RetType deal(Indata t,Retdata& r)
{
    r=balabala;
}

為多執行緒搞的那個為向主執行緒傳遞結果,不得不處理共享記憶體,現在你又要函式式操作,這絕逼不能夠啊,於是乎,你不得不又寫了一個:

RetType deal(Indata t)
{
Retdata r;
deal(t,r);
return r;
}

呵呵,我似乎聞到了壞程式碼的味道。

更好的方式

C++11中提供了操作多執行緒的高層次特性。

  • std::packaged_task 包裝的是一個非同步操作,相當與外包任務,好比我大阿里把電話客服外包給某某公司。
  • std::future 提供了一個訪問非同步操作結果的機制,這個是底層機制,在packaged_task和promise內部都有future來訪問結果。

說的比較乾巴,還是上程式碼吧!

#include <iostream>
#include<vector>
#include <future>
using namespace std;

long long jiechen(int n)
{
    long long ret=1;
    for (int i=1;i<=n; i++)
        ret*=i;
    return ret;

}

int main()
{
    vector<int> data={9,11,13,17,19};
    vector<future<long long>> fus;
    for (auto i:data)
    {
        packaged_task<long long() > pt(bind(jiechen,i));
        fus.push_back(std::move(pt.get_future()));
        std::thread(std::move(pt)).detach();
    }
    for(auto& i:fus)
    {
        i.wait();
        cout<<i.get()<<endl;
    }
    return 0;
}

比較簡單,就沒必要解釋了。跟“古老”的方案比,還是有些不同的

1. 不關心執行緒建立和結束
2. 使用層面無共享資料,這個意味著寫子執行緒函式時你不用小心翼翼擔心執行緒安全問題。
3. 抽象層次不同,古老方案本質上還是依靠負作用程式設計,新標準非同步方式則某種程度上跳出了這個範疇。
4,如有異常發生,新方案可以在外層處理異常。

上面說了那麼多,肯定還是有同學說,引入概念(計算機領域的概念真是多啊),變的難理解了,那有我直接操作執行緒幹活爽。我又想要好處,又想好理解,有簡單的方式嗎,有簡單的方式嗎,有簡單的方式嗎?
還真有:std::async

最佳實踐

std::async的原型async(std::launch::async | std::launch::deferred, f, args...)

std::launch::async:在呼叫async就開始建立執行緒。
std::launch::deferred:延遲載入方式建立執行緒。呼叫async時不建立執行緒,直到呼叫了future的get或者wait時才建立執行緒。

std::async是讓大家透懶的,工作過程是這樣的:async先將非同步操作用packaged_task包裝起來,然後將非同步操作的結果放到std::promise中,這個過程就是創造未來的過程。外面再通過future.get/wait來獲取這個未來的結果。完全可以理解為:
async(func,args).get() 就是啟動執行緒執行func並獲取返回值。
應用這個特性,上面的主執行緒程式碼變為:

int main()
{
    vector<int> data={9,11,13,17,19};
    vector<future<long long>> fus;
    for (auto i:data) fus.push_back(std::async(jiechen,i));
    for(auto& i:fus)
    {
        i.wait();
        cout<<i.get()<<endl;
    }

    return 0;
}

std:async 不是弄出來搗亂的玩意,真的是非同步程式設計利器,至少,在跨平臺時不用改程式碼不是。在專案中,大家應該盡力避免直接呼叫低層次介面,直接呼叫async建立新執行緒。
以上是一家之言,難免偏頗,歡迎探討。


相關文章