通過 “期望”實現的一個併發快排


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(&parallel_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;

}

代碼可編譯通過。

發佈了79 篇原創文章 · 獲贊 91 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章