C++11 之 並發編程 (一)


  未來芯片制造,如果突破不了 5nm 極限,則 CPU 性能的提升,可能會依賴於三維集成技術,將多個 CPU 核集成在一起,使得多核系統越來越普遍。

  以前的 C++ 多線程,一是受限於平台,多借助於封裝好的 APIs 來完成,例如:POSIX threads,Windows threads 等;二是受限於單核系統,本質上都是“偽多線程”:通過線程調度,使得單核系統進行任務的切換,形成多線程的假象。

  新的 C++11 標准,在語言層面上實現了多線程,其庫中提供了相關組件,使得跨平台編寫多線程 cpp 程序成為可能,最終能夠在多核系統中實現真正的並行計算

1  並發 (concurrency)

1.1 並發與並行

  計算機中的並發,指的是在單一系統中,同時執行多個獨立的活動。對於多核系統,它們在同一時刻進行的活動,則稱為並行

  通俗的理解:當有兩個人都在邊吃飯邊看電視時,對於一個人而言,吃飯和看電視就是並發,對於兩個人而言,他們在同一時刻進行的活動,可稱為並行。

 1) 單核的任務切換 (task switching)

 

 2) 雙核的並行執行 (parallel execution)

  

 1.2 線程與進程

  如果將進程比作一套房子,那么住在房子里的人,其從事的各種活動 (比如,廚房做飯,客廳吃飯,卧室看電視),就是一個個的線程。現在又搬來一個人,則當兩個人都在房子里,做着各自的活動時,程序便由以前的單線程變為了多線程

  有的房間 (比如客廳) 兩個人都可以同時進出,這代表着進程中某些內存空間是共享的,每個線程都可以使用這些共享內存。有的房間 (比如廁所) 一次只能容納一個人,先進去的把門鎖上,后到的人看到鎖,就在外面等待,直到先進去的人把鎖打開,這就是“互斥核” (mutex)

  在應用程序中,具體利用到並發編程的,一是多進程並發,二是多線程並發,如下圖:   

           

          (1) 多進程並發                     (2) 多線程並發

 

2  程序示例

  實現多線程需要 1) 頭文件 <thread>   2) 單獨的線程函數 threadFunc()   3)線程對象 thread t(threadFunc)   4)等待線程 join()

#include <thread>
#include <iostream>

void threadFunc()
{
    std::cout << "This is a thread!" << std::endl;
}

int main()
{
    std::thread t(threadFunc);
    t.join();
    
    std::cout << "This is main program!" << std::endl;
    
    return 0;
}

  輸出結果為:

This is a thread!
This is main program!

   當使用 t.detach() 來代替 t.join() 時,主線程 main 不會等待新線程 t(threadFunc),只會獨自運行到程序結束。

 

3  任務代替線程

3.1  兩個問題

1) 線程耗盡 (exhaustion)

 軟件線程是一種有限的資源,當創建的線程數量多於系統所能夠提供的,一個異常 std::system_error 就會拋出,程序便會終止。

2) 線程超額 (oversubscription)

 當等待運行的線程 (ready-to-run) 多於系統硬件線程 (hardware threads) 時,線程調度器會為每個軟件線程在硬件線程上分配時間片 (time-slice)。若一個線程的時間片結束,另一個線程的時間片剛開始時,上下文的切換 (context switch) 就會被執行。對於多核系統,當線程從一個 CPU 被切換到另一個 CPU 中時,會造成很大的資源消耗。

3.2  基於任務 (task-based)

  基於線程編程 (thread-based),必須手動管理上面的兩個問題,增加了編程的難度。

  基於任務編程 (task-based),通過 std::async,可將問題交由 C++標准庫處理,簡化了程序。

  1)  頭文件 <future> 

  2) 單獨的任務函數 taskFunc1taskFunc2 

  3) 異步任務對象 auto fut1 = std::async(taskFunc1) 

  4) 獲取任務函數返回值 fut1.get()

#include <iostream>
#include <thread>
#include <future>

std::string taskFunc1()
{
    std::string str = "This is task1";
    return str;
}

std::string taskFunc2()
{
    std::string str = " and task2";
    return str;
}

int main()
{
    auto fut1 = std::async(taskFunc1);
    auto fut2 = std::async(taskFunc2);

    std::cout << fut1.get() + fut2.get() << std::endl << "This is main program" << std::endl;

    return 0;
}

   輸出結果為:

This is task1 and task2
This is main program

 

小結:

 1) thread-based programming needs manual management of thread exhaustion, oversubscription, load balancing, and adaptation to new platforms.

 2) task-based programming handles most of these issues via std::async with the default launch policy

 

參考資料:

  <C++ Concurrency in Action> chapter 1

  <Effecctive Modern C++>  chapter 7


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM