當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > CPP開發(fā)者
[導(dǎo)讀]↓推薦關(guān)注↓為什么要并發(fā)編程大型的軟件項(xiàng)目常常包含非常多的任務(wù)需要處理。例如:對(duì)于大量數(shù)據(jù)的數(shù)據(jù)流處理,或者是包含復(fù)雜GUI界面的應(yīng)用程序。如果將所有的任務(wù)都以串行的方式執(zhí)行,則整個(gè)系統(tǒng)的效率將會(huì)非常低下,應(yīng)用程序的用戶體驗(yàn)會(huì)非常的差。另一方面,自上個(gè)世紀(jì)六七十年代英特爾創(chuàng)始人之...


推薦關(guān)注↓


為什么要并發(fā)編程


大型的軟件項(xiàng)目常常包含非常多的任務(wù)需要處理。例如:對(duì)于大量數(shù)據(jù)的數(shù)據(jù)流處理,或者是包含復(fù)雜GUI界面的應(yīng)用程序。如果將所有的任務(wù)都以串行的方式執(zhí)行,則整個(gè)系統(tǒng)的效率將會(huì)非常低下,應(yīng)用程序的用戶體驗(yàn)會(huì)非常的差。
另一方面,自上個(gè)世紀(jì)六七十年代英特爾創(chuàng)始人之一 Gordon Moore 提出?摩爾定義?以來(lái),CPU頻率以每18個(gè)月翻一番的指數(shù)速度增長(zhǎng)。但這一增長(zhǎng)在最近的十年已經(jīng)基本停滯,大家會(huì)發(fā)現(xiàn)曾經(jīng)有過(guò)一段時(shí)間CPU的頻率從3G到達(dá)4G,但在這之后就停滯不前了。因此最近的新款CPU也基本上都是3G左右的頻率。相應(yīng)的,CPU以更多核的形式在增長(zhǎng)。目前的Intel i7有8核的版本,Xeon處理器達(dá)到了28核。并且,最近幾年手機(jī)上使用的CPU也基本上是4核或者8核的了。
由此,掌握并發(fā)編程技術(shù),利用多處理器來(lái)提升軟件項(xiàng)目的性能將是軟件工程師的一項(xiàng)基本技能。
本文以C 語(yǔ)言為例,講解如何進(jìn)行并發(fā)編程。并盡可能涉及C 11,C 14以及C 17中的主要內(nèi)容。

并發(fā)與并行


并發(fā)(Concurrent)與并行(Parallel)都是很常見的術(shù)語(yǔ)。
Erlang之父Joe Armstrong曾經(jīng)以人們使用咖啡機(jī)的場(chǎng)景為例描述了這兩個(gè)術(shù)語(yǔ)。如下圖所示:

  • 并發(fā):如果多個(gè)隊(duì)列可以交替使用某臺(tái)咖啡機(jī),則這一行為就是并發(fā)的。
  • 并行:如果存在多臺(tái)咖啡機(jī)可以被多個(gè)隊(duì)列交替使用,則就是并行。

這里隊(duì)列中的每個(gè)人類比于計(jì)算機(jī)的任務(wù),咖啡機(jī)類比于計(jì)算機(jī)處理器。因此:并發(fā)和并行都是在多任務(wù)的環(huán)境下的討論。
更嚴(yán)格的來(lái)說(shuō):如果一個(gè)系統(tǒng)支持多個(gè)動(dòng)作同時(shí)存在,那么這個(gè)系統(tǒng)就是一個(gè)并發(fā)系統(tǒng)。如果這個(gè)系統(tǒng)還支持多個(gè)動(dòng)作(物理時(shí)間上)同時(shí)執(zhí)行,那么這個(gè)系統(tǒng)就是一個(gè)并行系統(tǒng)。
你可能已經(jīng)看出,“并行”其實(shí)是“并發(fā)”的子集。它們的區(qū)別在于是否具有多個(gè)處理器。如果存在多個(gè)處理器同時(shí)執(zhí)行多個(gè)線程,就是并行。
在不考慮處理器數(shù)量的情況下,我們統(tǒng)稱之為“并發(fā)”。

進(jìn)程與線程


進(jìn)程與線程是操作系統(tǒng)的基本概念。無(wú)論是桌面系統(tǒng):MacOS,Linux,Windows,還是移動(dòng)操作系統(tǒng):Android,iOS,都存在進(jìn)程和線程的概念。
進(jìn)程(英語(yǔ):process),是指計(jì)算機(jī)中已運(yùn)行的程序。進(jìn)程為曾經(jīng)是分時(shí)系統(tǒng)的基本運(yùn)作單位。在面向進(jìn)程設(shè)計(jì)的系統(tǒng)(如早期的UNIX,Linux 2.4及更早的版本)中,進(jìn)程是程序的基本執(zhí)行實(shí)體;線程(英語(yǔ):thread)是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。-- 維基百科

關(guān)于這兩個(gè)概念在任何一本操作系統(tǒng)書上都可以找到定義。網(wǎng)上也有很多文章對(duì)它們進(jìn)行了解釋。因此這里不再贅述,這里僅僅提及一下它們與編程的關(guān)系。
對(duì)于絕大部分編程語(yǔ)言或者編程環(huán)境來(lái)說(shuō),我們所寫的程序都會(huì)在一個(gè)進(jìn)程中運(yùn)行。一個(gè)進(jìn)程至少會(huì)包含一個(gè)線程。這個(gè)線程我們通常稱之為主線程。
在默認(rèn)的情況下,我們寫的代碼都是在進(jìn)程的主線程中運(yùn)行,除非開發(fā)者在程序中創(chuàng)建了新的線程。
不同編程語(yǔ)言的線程環(huán)境會(huì)不一樣,Java語(yǔ)言在很早就支持了多線程接口。(Java程序在Java虛擬機(jī)中運(yùn)行,虛擬機(jī)通常還會(huì)包含自己特有的線程,例如垃圾回收線程。)。而對(duì)于JavaScript這樣的語(yǔ)言來(lái)說(shuō),它就沒有多線程的概念。
當(dāng)我們只有一個(gè)處理器時(shí),所有的進(jìn)程或線程會(huì)分時(shí)占用這個(gè)處理器。但如果系統(tǒng)中存在多個(gè)處理器時(shí),則就可能有多個(gè)任務(wù)并行的運(yùn)行在不同的處理器上。
下面兩幅圖以不同顏色的矩形代表不同的任務(wù)(可能是進(jìn)程,也可能是線程)來(lái)描述它們可能在處理器上執(zhí)行的順序。
下圖是單核處理器的情況:

下面是四核處理器的情況:

任務(wù)會(huì)在何時(shí)占有處理器,通常是由操作系統(tǒng)的調(diào)度策略決定的。在《Android系統(tǒng)上的進(jìn)程管理:進(jìn)程的調(diào)度》一文中,我們介紹過(guò)Linux的調(diào)度策略。
當(dāng)我們?cè)陂_發(fā)跨平臺(tái)的軟件時(shí),我們不應(yīng)當(dāng)對(duì)調(diào)度策略做任何假設(shè),而應(yīng)該抱有“系統(tǒng)可能以任意順序來(lái)調(diào)度我的任務(wù)”這樣的想法。

并發(fā)系統(tǒng)的性能


開發(fā)并發(fā)系統(tǒng)最主要的動(dòng)機(jī)就是提升系統(tǒng)性能(事實(shí)上,這是以增加復(fù)雜度為代價(jià)的)。
但我們需要知道,單純的使用多線程并不一定能提升系統(tǒng)性能(當(dāng)然,也并非線程越多系統(tǒng)的性能就越好)。從上面的兩幅圖我們就可以直觀的感受到:線程(任務(wù))的數(shù)量要根據(jù)具體的處理器數(shù)量來(lái)決定。假設(shè)只有一個(gè)處理器,那么劃分太多線程可能會(huì)適得其反。因?yàn)楹芏鄷r(shí)間都花在任務(wù)切換上了。
因此,在設(shè)計(jì)并發(fā)系統(tǒng)之前,一方面我們需要做好對(duì)于硬件性能的了解,另一方面需要對(duì)我們的任務(wù)有足夠的認(rèn)識(shí)。
關(guān)于這一點(diǎn),你可能需要了解一下阿姆達(dá)爾定律了。對(duì)于這個(gè)定律,簡(jiǎn)單來(lái)說(shuō):我們想要預(yù)先意識(shí)到那些任務(wù)是可以并行的,那些是無(wú)法并行的。只有明確了任務(wù)的性質(zhì),才能有的放矢的進(jìn)行優(yōu)化。這個(gè)定律告訴了我們將系統(tǒng)并行之后性能收益的上限。
關(guān)于阿姆達(dá)爾定律在Linux系統(tǒng)監(jiān)測(cè)工具sysstat介紹一文中已經(jīng)介紹過(guò),因此這里不再贅述。

C 與并發(fā)編程


前面我們已經(jīng)了解到,并非所有的語(yǔ)言都提供了多線程的環(huán)境。
即便是C 語(yǔ)言,直到C 11標(biāo)準(zhǔn)之前,也是沒有多線程支持的。在這種情況下,Linux/Unix平臺(tái)下的開發(fā)者通常會(huì)使用POSIX Threads,Windows上的開發(fā)者也會(huì)有相應(yīng)的接口。但很明顯,這些API都只針對(duì)特定的操作系統(tǒng)平臺(tái),可移植性較差。如果要同時(shí)支持Linux和Windows系統(tǒng),你可能要寫兩套代碼。
相較而言,Java自JDK 1.0就包含了多線程模型。

這個(gè)狀態(tài)在C 11標(biāo)準(zhǔn)發(fā)布之后得到了改變。并且,在C 14和C 17標(biāo)準(zhǔn)中又對(duì)并發(fā)編程機(jī)制進(jìn)行了增強(qiáng)。
下圖是最近幾個(gè)版本的C 標(biāo)準(zhǔn)特性的線路圖。

編譯器與C 標(biāo)準(zhǔn)


編譯器對(duì)于語(yǔ)言特性的支持是逐步完成的。想要使用特定的特性你需要相應(yīng)版本的編譯器。
  • GCC對(duì)于C 特性的支持請(qǐng)參見這里:C Standards Support in GCC。
  • Clang對(duì)于C 特性的支持請(qǐng)參見這里:C Support in Clang。

下面兩個(gè)表格列出了C 標(biāo)準(zhǔn)和相應(yīng)編譯器的版本對(duì)照:
  • C 標(biāo)準(zhǔn)與相應(yīng)的GCC版本要求如下:
  • C 標(biāo)準(zhǔn)與相應(yīng)的Clang版本要求如下:

默認(rèn)情況下編譯器是以較低的標(biāo)準(zhǔn)來(lái)進(jìn)行編譯的,如果希望使用新的標(biāo)準(zhǔn),你需要通過(guò)編譯參數(shù)-std=c xx告知編譯器,例如:
g -std=c 17 your_file.cpp -o your_program

測(cè)試環(huán)境


本文的源碼可以到下載我的github上獲取,地址:paulQuei/cpp-concurrency。你可以直接通過(guò)下面這條命令獲取源碼:
git clone https://github.com/paulQuei/cpp-concurrency.git
源碼下載之后,你可以通過(guò)任何文本編輯器瀏覽源碼。如果希望編譯和運(yùn)行程序,你還需要按照下面的內(nèi)容來(lái)準(zhǔn)備環(huán)境。
本文中的源碼使用cmake編譯,只有cmake 3.8以上的版本才支持C 17,所以你需要安裝這個(gè)或者更新版本的cmake。
另外,截止目前(2019年10月)為止,clang編譯器還不支持并行算法。
但是gcc-9是支持的。因此想要編譯和運(yùn)行這部分代碼,你需要安裝gcc 9.0或更新的版本。并且,gcc-9還要依賴Intel Threading Building Blocks才能使用并行算法以及頭文件。
具體的安裝方法見下文。
具體編譯器對(duì)于C 特性支持的情況請(qǐng)參見這里:C compiler support。

安裝好之后運(yùn)行根目錄下的下面這個(gè)命令即可:
./make_all.sh
它會(huì)完成所有的編譯工作。
本文的源碼在下面兩個(gè)環(huán)境中經(jīng)過(guò)測(cè)試,環(huán)境的準(zhǔn)備方法如下。

MacOS


在Mac上,我使用brew工具安裝gcc以及tbb庫(kù)。
考慮到其他人與我的環(huán)境可能會(huì)有所差異,所以需要手動(dòng)告知tbb庫(kù)的安裝路徑。讀者需要執(zhí)行下面這些命令來(lái)準(zhǔn)備環(huán)境:

rew install gccbrew insbtall tbb
export tbb_path=/usr/local/Cellar/tbb/2019_U8/./make_all.sh
注意,請(qǐng)通過(guò)運(yùn)行g(shù) -9命令以確認(rèn)gcc的版本是否正確,如果版本較低,則需要通過(guò)brew命令將其升級(jí)到新版本:
brew upgrade gcc

Ubuntu


Ubuntu上,通過(guò)下面的命令安裝gcc-9。
sudo add-apt-repository ppa:ubuntu-toolchain-r/testsudo apt-get updatesudo apt install gcc-9 g -9
但安裝tbb庫(kù)就有些麻煩了。這是因?yàn)閁buntu 16.04默認(rèn)關(guān)聯(lián)的版本是較低的,直接安裝是無(wú)法使用的。我們需要安裝更新的版本。聯(lián)網(wǎng)安裝的方式步驟繁瑣,所以可以通過(guò)下載包的方式進(jìn)行安裝,我已經(jīng)將這需要的兩個(gè)文件放到的這里:
  • libtbb2_2019~U8-1_amd64.deb
  • libtbb-dev_2019~U8-1_amd64.deb

如果需要,你可以下載后通過(guò)apt命令安裝即可:
sudo apt install ~/Downloads/libtbb2_2019~U8-1_amd64.deb sudo apt install ~/Downloads/libtbb-dev_2019~U8-1_amd64.deb

線程


創(chuàng)建線程


創(chuàng)建線程非常的簡(jiǎn)單的,下面就是一個(gè)使用了多線程的Hello World示例:
// 01_hello_thread.cpp
#include #include // ①
using namespace std; // ②
void hello() { // ③ cout << "Hello World from new thread." << endl;}
int main() { thread t(hello); // ④ t.join(); // ⑤
return 0;}
對(duì)于這段代碼說(shuō)明如下:
  1. 為了使用多線程的接口,我們需要#include 頭文件。
  2. 為了簡(jiǎn)化聲明,本文中的代碼都將using namespace std;。
  3. 新建線程的入口是一個(gè)普通的函數(shù),它并沒有什么特別的地方。
  4. 創(chuàng)建線程的方式就是構(gòu)造一個(gè)thread對(duì)象,并指定入口函數(shù)。與普通對(duì)象不一樣的是,此時(shí)編譯器便會(huì)為我們創(chuàng)建一個(gè)新的操作系統(tǒng)線程,并在新的線程中執(zhí)行我們的入口函數(shù)。
  5. 關(guān)于join函數(shù)在下文中講解。

thread可以和callable類型一起工作,因此如果你熟悉lambda表達(dá)式,你可以直接用它來(lái)寫線程的邏輯,像這樣:
// 02_lambda_thread.cpp
#include #include
using namespace std;
int main() { thread t([] { cout << "Hello World from lambda thread." << endl; });
t.join();
return 0;}
為了減少不必要的重復(fù),若無(wú)必要,下文中的代碼將不貼出include指令以及using聲明。

當(dāng)然,你可以傳遞參數(shù)給入口函數(shù),像下面這樣:
// 03_thread_argument.cpp
void hello(string name) { cout << "Welcome to " << name << endl;}
int main() { thread t(hello, "https://paul.pub"); t.join();
return 0;}
不過(guò)需要注意的是,參數(shù)是以拷貝的形式進(jìn)行傳遞的。因此對(duì)于拷貝耗時(shí)的對(duì)象你可能需要傳遞指針或者引用類型作為參數(shù)。但是,如果是傳遞指針或者引用,你還需要考慮參數(shù)對(duì)象的生命周期。因?yàn)榫€程的運(yùn)行長(zhǎng)度很可能會(huì)超過(guò)參數(shù)的生命周期(見下文detach),這個(gè)時(shí)候如果線程還在訪問(wèn)一個(gè)已經(jīng)被銷毀的對(duì)象就會(huì)出現(xiàn)問(wèn)題。

join與detach


  • 主要API

一旦啟動(dòng)線程之后,我們必須決定是要等待直接它結(jié)束(通過(guò)join),還是讓它獨(dú)立運(yùn)行(通過(guò)detach),我們必須二者選其一。如果在thread對(duì)象銷毀的時(shí)候我們還沒有做決定,則thread對(duì)象在析構(gòu)函數(shù)出將調(diào)用std::terminate()從而導(dǎo)致我們的進(jìn)程異常退出。
請(qǐng)思考在上面的代碼示例中,thread對(duì)象在何時(shí)會(huì)銷毀。

需要注意的是:在我們做決定的時(shí)候,很可能線程已經(jīng)執(zhí)行完了(例如上面的示例中線程的邏輯僅僅是一句打印,執(zhí)行時(shí)間會(huì)很短)。新的線程創(chuàng)建之后,究竟是新的線程先執(zhí)行,還是當(dāng)前線程的下一條語(yǔ)句先執(zhí)行這是不確定的,因?yàn)檫@是由操作系統(tǒng)的調(diào)度策略決定的。不過(guò)這不要緊,我們只要在thread對(duì)象銷毀前做決定即可。
  • join:調(diào)用此接口時(shí),當(dāng)前線程會(huì)一直阻塞,直到目標(biāo)線程執(zhí)行完成(當(dāng)然,很可能目標(biāo)線程在此處調(diào)用之前就已經(jīng)執(zhí)行完成了,不過(guò)這不要緊)。因此,如果目標(biāo)線程的任務(wù)非常耗時(shí),你就要考慮好是否需要在主線程上等待它了,因此這很可能會(huì)導(dǎo)致主線程卡住。
  • detach:detach是讓目標(biāo)線程成為守護(hù)線程(daemon threads)。一旦detach之后,目標(biāo)線程將獨(dú)立執(zhí)行,即便其對(duì)應(yīng)的thread對(duì)象銷毀也不影響線程的執(zhí)行。并且,你無(wú)法再與之通信。

對(duì)于這兩個(gè)接口,都必須是可執(zhí)行的線程才有意義。你可以通過(guò)joinable()接口查詢是否可以對(duì)它們進(jìn)行join或者detach。

管理當(dāng)前線程


  • 主要API

上面是一些在線程內(nèi)部使用的API,它們用來(lái)對(duì)當(dāng)前線程做一些控制。
  • yield 通常用在自己的主要任務(wù)已經(jīng)完成的時(shí)候,此時(shí)希望讓出處理器給其他任務(wù)使用。
  • get_id 返回當(dāng)前線程的id,可以以此來(lái)標(biāo)識(shí)不同的線程。
  • sleep_for 是讓當(dāng)前線程停止一段時(shí)間。
  • sleep_until 和sleep_for類似,但是是以具體的時(shí)間點(diǎn)為參數(shù)。這兩個(gè)API都以chrono API(由于篇幅所限,這里不展開這方面內(nèi)容)為基礎(chǔ)。

下面是一個(gè)代碼示例:
// 04_thread_self_manage.cpp
void print_time() { auto now = chrono::system_clock::now(); auto in_time_t = chrono::system_clock::to_time_t(now);
std::stringstream ss; ss << put_time(localtime(
本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來(lái)越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來(lái)越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽(yáng)舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語(yǔ)權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉