1、 什麼是期望;
假設你乘飛機去國外度假。當你到達機場,並且辦理完各種登機手續後,你還需要等待機場廣播通知你登機,可能要等很多個小時。你可能會在候機室裏面找一些事情來打發時間,比如:讀書,上網,或者來一杯價格不菲的機場咖啡,不過從根本上來說你就在等待一件事情:機場廣播能夠登機的時間。給定的飛機班次再之後沒有可參考性;當你在再次度假的時候,你可能會等待另一班飛機。
C++標準庫模型將這種一次性事件稱爲“期望” (future)。當一個線程需要等待一個特定的一次性事件時,在某種程度上來說它就需要知道這個事件在未來的表現形式。
2、原理
在C++標準庫中,有兩種“期望”,使用兩種類型模板實現,聲明在頭文件中: 唯一期望(uniquefutures)( std::future<> )和共享期望(shared futures)( std::shared_future<> ),這是仿照 std::unique_ptr 和 std::shared_ptr 實現的。
std::future 的實例只能與一個指定事件相關聯,而std::shared_future 的實例就能關聯多個事件
3、 快排
同步操作可以簡化代碼,可以使用“期望”的函數化編程。
先看一個快速排序——順序實現版:
template<typenameT>
std::list<T>CSort::sequential_quick_sort(std::list<T>input)
{
if (input.empty())
{
return input;
}
std::list<T>result;
result.splice(result.begin(),input, input.begin());// 1 使用splice()①將輸入的首個元素(中間值)放入結果列表中
T const&pivot = *result.begin();// 2使用“中間”值進行比較,所以這裏使用了一個引用,爲了避免過多的拷貝
auto divide_point = std::partition(input.begin(),input.end(),
[&](T const& t) { returnt < pivot; }); // 3 將序列中的值分成小於“中間”值的組和大於“中間”值的組,最簡單的方法就是使用lambda函數指定區分的標準
std::list<T>lower_part;
lower_part.splice(lower_part.end(),input, input.begin(),
divide_point); // 4 當要使用遞歸對兩部分排序時, 你將需要創建兩個列表 用splice()函數來做這件事,將input列表小於divided_point的值移動到新列表lower_part中
auto new_lower(
sequential_quick_sort(std::move(lower_part)));// 5 這裏顯式使用 std::move()將列表傳遞到類函數中(移動語義),這種方式還是爲了避免大量的拷貝操作。
auto new_higher(sequential_quick_sort(std::move(input)));// 6
result.splice(result.end(),new_higher); // 7 new_higher指向的值放在“中間”值的後面,new_lower指向的值放在“中間”值的前面.
result.splice(result.begin(),new_lower); // 8 最終,你可以再次使用splice(),將result中的結果以正確的順序進行拼接。
return result;
}
快速排序 FP模式線程強化版
因爲還是使用函數化模式,所以使用“期望”很容易將其轉化爲並行的版本,如下面的程序清單所示。其中的操作與前面相同,不同的是它們現在並行運行。
template<typenameT>
std::list<T>CSort::parallel_quick_sort(std::list<T>input)
{
if (input.empty())
{
return input;
}
std::list<T>result;
result.splice(result.begin(),input, input.begin());
T const&pivot = *result.begin();
auto divide_point = std::partition(input.begin(),input.end(),
[&](T const& t) {returnt < pivot; });
std::list<T>lower_part;
lower_part.splice(lower_part.end(),input, input.begin(),
divide_point);
//以上部分和上一個函數一樣
//主要在以下部分。期望併發;
/*
1 這裏最大的變化是,當前線程不對小於“中間”值部分的列表進行排序,使用 std::async()在另一線程對其進行排序。
std::async()會啓動一個新線程,這樣當你遞歸三次時,就會有八個線程在運行了;當你遞歸十次(對於大約有1000個元素的列表),
如果硬件能處理這十次遞歸調用,你將會創建1024個執行線程。當運行庫認爲這樣做產生了太多的任務時(也許是因爲數量超過了硬件併發的最大值),
運行庫可能會同步的切換新產生的任務。
當任務過多時(已影響性能),這些任務應該在使用get()函數獲取的線程上運行,而不是在新線程上運行,這樣就能避免任務向線程傳遞的開銷
*/
std::future<std::list<T>>new_lower(std::async(¶llel_quick_sort<T>,std::move(lower_part)));// 1
auto new_higher(parallel_quick_sort(std::move(input)));// 2大於部分列表 如同之前一樣,使用遞歸的方式進行排序,通過遞歸調用parallel_quick_sort(),你就可以利用可用的硬件併發了。
result.splice(result.end(),new_higher); // 3
result.splice(result.begin(),new_lower.get());// 4
return result;
}
代碼可編譯通過。