[C++併發編程] 1. 併發編程入門

本系列記錄學習C++併發編程過程中的一些歸納總結的筆記。

併發編程

對併發編程最簡單地說明就是CPU同時處理兩個或更多獨立的任務。

那麼我們爲什麼需要併發編程呢?舉個簡單的例子,如果你想開發一個界面應用程序,這個程序需要若干個存有100萬個數據的CSV文件進行處理,然後將處理完的數據寫入到另外的文件,那麼這個程序的任務就可以分爲三個小部分:導入CSV文件,處理數據,寫出數據,界面顯示進度(導入/寫出),如果不使用併發,那麼需要先等所有的CSV文件導入後,然後處理數據,再處理數據的同時更新數據處理的進度,然後處理下一個數據之前需要等待當前數據寫入到文件,這樣的話,在處理一個任務的時候,另外的任務會處於“僵死”的狀態。比如處理數據的時候,界面上的按鈕將無法使用,點擊界面上控件的時候,數據將無法被處理。

如果使用併發編程,那這些問題將得到解決,讀取CSV文件,處理數據,處理頁面響應,寫出到文件都可以單獨進行,這樣既處理好了任務,與用戶的實時交互也變得友好了。

那麼,開始併發之前,有兩個概念需要搞清楚,即“多處理器”和“多核”。多處理器是指在一臺電腦上存在有多個物理CPU,這樣的配置即使是現在也基本上只會在服務器上使用;而多核,也可以說多核處理器,是指只有一個物理CPU,但是在這個CPU中做了多個核心,每個核心就相當於一個個的小CPU,這樣的多核心CPU普遍存在於我們現在的普通家用計算機中,可以在計算機的設備管理器中查看自己的電腦是幾核的計算機。
在設備管理器中查看處理器信息
或者也可以在程序中進行查看

#include <iostream>
#include <thread>

int main()
{
    std::thread t();
    std::cout << t.hardware_concurrency() << std::endl;
    t.join();
	return 0;
}
// 程序會打印出 8 ,意味着運行該程序的計算機搭載的是8核CPU。

在多處理器機中,每個處理器處理着不同的任務,不互相干擾,可以實現真正意義上的併發,但是在早期的單處理器時代,只有一個單核CPU,實現的併發只是表面上看起來併發,實際上,要執行的每個任務都被分配了細微到人感受不到的微小時間片,反過來說,在每個時間片,執行的任務可能是不同的,相鄰的兩個時間片的任務不同時,則中間還會存在一定的任務切換時間。
雙核和單核
上圖中綠色和紅色分別代表一項任務,如果這兩項任務由一個雙核處理器來完成的話,那麼可以每一個核心處理一個任務,互補干擾;如果是一個單核處理器來完成的話,那麼爲了能使兩個任務“同時”進行,則需要將時間分成很小的片,每個時間片交替處理兩個任務,在單核處理器處理過程中還存在灰色的小時間片,這是在兩個任務之間切換(task switching)所花的時間。

多進程與多線程

講併發,一般會想到兩種,一種是多進程併發,一種是多線程併發。

首先,什麼是進程?我們自己寫的可以運行的代碼,保存在電腦的硬盤上,叫做程序,但是,一旦一個可執行的程序跑起來了,那麼這段代碼就叫做一個進程,簡單說,進程就是在計算機上運行起來的代碼。比如說IE瀏覽器,Word文檔等,這就是不同的進程,但是它們可以同時運行,這就是多進程,進程與進程之間可以通過多種方法傳遞信息,如信號,socket,文件,管道等,這就是進程間通信。

線程是一種比進程更小一級的單位,可以理解爲進程中的一個個子任務,一個進程可以包含多個線程,每個線程也是獨立運行的,但是與多進程不同的是,線程與線程之間共享地址空間,且所有線程能訪問到大部分數據。

進程與線程的對比
多線程相比起多進程,共享地址空間,在切換任務的時候,多線程花費更少的時間成本,操作系統開銷更小,但是由於數據指令共享地址空間,也會帶來複雜度提高的缺點,處理更加的複雜。

接下來我主要針對多線程併發進行了學習,多進程學習以後再在別的博客裏再寫。

Hello World

一個簡單的C++多線程程序

#include <iostream>
#include <thread>

void helloworld()
{
    std::cout << "Hello World" << std::endl;
}

int main()
{
    std::thread t(helloworld);
    t.join();
    return 0;
}

上面的程序與普通的C++ Hello World 程序不同的地方在於

  • 頭文件添加了 <thread> 頭文件,<thread> 頭文件包含了 thread類的定義和相應成員函數的定義,如 join(), get_id()等,因此在程序中可以使用 std::thread 來定義線程類。
  • 使用std::thread 來定義線程類,定義線程類的時候,可以附帶參數,這個例子中的參數 helloworld 爲線程起始函數(initial function),既線程啓動時,需要從哪個函數開始執行。

其實,一個進程啓動時,會有一個主線程啓動(main thread),這個線程函數從程序開始自動生成,程序終止時自動消亡,所以main thread從main函數開始執行,即main函數爲主線程的起始函數。

  • join()函數表示阻塞,阻塞的意思就是等待 t 線程執行完畢後,當前線程再繼續執行,所以當main函數中遇到t.join()這語句時,主線程暫停執行,等待 t 線程執行完畢,打印出 Hello World後,再繼續執行。

執行的流程圖如下
主線程與子線程在有阻塞的情況下的執行順序

總結

多線程編程優點:

  • 一個進程中的所有線程共享地址空間,全局變量,指針,引用都可以在線程之間傳遞,所以使用多線程開銷遠遠小於多進程。
  • 比起多進程,多線程啓動速度更快,更輕量級。

不足的地方:

  • 共享內存帶來數據一致性問題。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章