為什么使用并發(fā)
在應(yīng)用程序中使用并發(fā)的原因主要有兩個:關(guān)注點(diǎn)分離和性能。事實(shí)上,我甚至可以說它們差不多是使用并發(fā)的唯一原因;當(dāng)你觀察得足夠仔細(xì)時,一切其他因素都可以歸結(jié)到這兩者之一(或者可能是二者兼有,當(dāng)然,除了像“我愿意”這樣的原因之外)。

為了劃分關(guān)注點(diǎn)而使用并發(fā)
在編寫軟件時,劃分關(guān)注點(diǎn)總是個好主意。通過將相關(guān)的代碼放在一起并將無關(guān)的代碼分開,這種方法可以使你的程序更容易理解和測試,從而減少出錯的可能性。你可以使用并發(fā)來分隔不同的功能區(qū)域,即使在這些不同功能區(qū)域的操作需要在同一時刻發(fā)生的情況下。如果不顯式地使用并發(fā),你要么被迫編寫任務(wù)切換框架,要么在操作中主動地調(diào)用不相關(guān)的一段代碼。
考慮一類帶有用戶界面的密集處理型應(yīng)用程序,例如為臺式計(jì)算機(jī)提供的DVD播放程序。這樣一個應(yīng)用程序基本上具備兩套職能:它不僅要從光盤中讀取數(shù)據(jù),解碼圖像和聲音,并把它們及時輸出至視頻和音頻硬件,從而實(shí)現(xiàn)DVD的無錯播放;它還要接受來自用戶的輸入,例如當(dāng)用戶單擊暫停或返回菜單甚至退出按鍵的情況。在單個線程中,應(yīng)用程序須在回放期間定期檢查用戶的輸入,于是將DVD回放代碼和用戶界面代碼合在一起。通過使用多線程來分隔這些關(guān)注點(diǎn),用戶界面代碼和DVD回放代碼不再需要如此緊密地交織在一起。一個線程可以處理用戶界面,另一個處理DVD回放,它們之間會有交互,例如用戶點(diǎn)擊暫停,但現(xiàn)在這些交互直接與眼前的任務(wù)有關(guān)。
這會帶來響應(yīng)性的錯覺,因?yàn)橛脩艚缑婢€程通常可以立即響應(yīng)用戶的請求,即使在請求被傳達(dá)給工作的線程,響應(yīng)為簡單地顯示正忙的光標(biāo)或請等待的消息的情況。類似地,獨(dú)立的線程常被用于運(yùn)行必須在后臺連續(xù)運(yùn)行的任務(wù),例如在桌面搜索程序中監(jiān)視文件系統(tǒng)的變化。以這種方式使用線程一般會使每個線程的邏輯更加簡單,因?yàn)樗鼈冎g的交互可以被限制為清晰可辨的點(diǎn),而不是到處散播不同任務(wù)的邏輯。
在這種情況下,線程的數(shù)量與CPU可用內(nèi)核的數(shù)量無關(guān),因?yàn)閷€程的劃分是基于概念上的設(shè)計(jì)而不是試圖增加吞吐量。
為了性能而使用并發(fā)
多處理器系統(tǒng)已經(jīng)存在了幾十年,但直到最近,他們幾乎只能在超級計(jì)算機(jī)、大型機(jī)和大型服務(wù)器系統(tǒng)中才能看到。然而芯片制造商越來越傾向于多核芯片的設(shè)計(jì),即在單個芯片上集成2、4、16或更多的處理器,從而達(dá)到比單核心更好的性能。因此,多核臺式計(jì)算機(jī),甚至多核嵌入式設(shè)備,現(xiàn)在越來越普遍。這些計(jì)算機(jī)的計(jì)算能力的提高不是源自使單一任務(wù)運(yùn)行的更快,而是源自并行運(yùn)行多個任務(wù)。在過去,程序員曾坐等他們的程序隨著處理器的更新?lián)Q代而變得更快,無需他們這邊做出任何努力。但是現(xiàn)在,就像Herb Sutter所說的,“免費(fèi)的午餐結(jié)束了1”。如果軟件想要利用日益增長的計(jì)算能力,它必須設(shè)計(jì)為并發(fā)運(yùn)行多個任務(wù)。程序員因此必須留意,而且那些迄今都忽略并發(fā)的人們必須注意它并將其加入他們的工具箱中。
有兩種方式為了性能使用并發(fā)。首先,也是最明顯的,是將一個單個任務(wù)分成幾部分且各自并行運(yùn)行,從而降低總運(yùn)行時間,這就是任務(wù)并行(taskparallelism)。雖然這聽起來很直觀,但它可以是一個相當(dāng)復(fù)雜的過程,因?yàn)樵诟鱾€部分之間可能存在很多的依賴。區(qū)別可能是在過程方面——一個線程執(zhí)行算法的一部分而另一個線程執(zhí)行算法的另一部分——或是在數(shù)據(jù)方面——每個線程在不同的數(shù)據(jù)部分上執(zhí)行相同的操作。后一種方法被稱為數(shù)據(jù)并行(dataparallelism)。
容易受這種并行影響的算法常被稱為易并行(embarrassinglyparallel)。拋開你可能會尷尬地面對的很容易并行化的代碼這一含義,這是一件好事情。我曾遇到過的關(guān)于此算法的別的術(shù)語是自然并行(naturallyparallel)和便利并發(fā)(convenientlyconcurrent)。易并行算法具有良好的可擴(kuò)展特性——隨著可用硬件線程數(shù)量的提升,算法的并行性可以隨之增加與之匹配。這樣的一個算法是諺語“人多力量大”的完美體現(xiàn)。對于非易并行算法的那一部分,你可以將算法劃分為一個固定(因而不可擴(kuò)展)數(shù)量的并行任務(wù)。在線程之間劃分任務(wù)的技巧涵蓋在第8章中。
使用并發(fā)來提升性能的第二種方法是使用可用的并行方式來解決更大的問題。與其同時處理一個文件,不如酌情處理2個或10個或20個。雖然這實(shí)際上只是數(shù)據(jù)并行的一種應(yīng)用,通過對多組數(shù)據(jù)同時執(zhí)行相同的操作,但還是有不同的重點(diǎn)。處理一個數(shù)據(jù)塊仍然需要同樣的時間,但在相同的時間內(nèi)卻可以處理更多的數(shù)據(jù)。當(dāng)然,這種方法也存在限制,且并非在所有情況下都是有益的,但是這種方法所帶來的吞吐量提升可以讓一些新玩意變得可能。例如,如果圖片的各部分可以并行處理,就能提高視頻處理的分辨率。
什么時候不使用并發(fā)
知道何時不使用并發(fā)與知道何時要使用它同等重要。基本上,不使用并發(fā)的唯一原因就是在收益比不上成本的時候。使用并發(fā)的代碼在很多情況下難以理解,因此編寫和維護(hù)的多線程代碼就有直接的腦力成本,同時額外的復(fù)雜性也可能導(dǎo)致更多的錯誤。除非潛在的性能增益足夠大或關(guān)注點(diǎn)分離得足夠清晰,能抵消確保其正確所需的額外的開發(fā)時間以及與維護(hù)多線程代碼相關(guān)的額外成本,否則不要使用并發(fā)。
同樣地,性能增益可能不會如預(yù)期的那么大。在啟動線程時存在固有的開銷,因?yàn)椴僮飨到y(tǒng)必須分配相關(guān)的內(nèi)核資源和堆棧空間,然后將新線程加入調(diào)度器中,所有這一切都要占用時間。如果在線程上運(yùn)行的任務(wù)完成得很快,那么任務(wù)實(shí)際上占據(jù)的時間與啟動線程的開銷時間相比就顯得微不足道,可能會導(dǎo)致應(yīng)用程序的整體性能還不如通過產(chǎn)生線程直接執(zhí)行該任務(wù)。
此外,線程是有限的資源。如果讓太多的線程同時運(yùn)行,則會消耗操作系統(tǒng)資源,并且使得操作系統(tǒng)整體上運(yùn)行得更緩慢。不僅如此,運(yùn)行太多的線程會耗盡進(jìn)程的可用內(nèi)存或地址空間,因?yàn)槊總€線程都需要一個獨(dú)立的堆棧空間。對于一個可用地址空間限制為4GB的扁平架構(gòu)的32位進(jìn)程來說,這尤其是個問題。如果每個線程都有一個1MB的堆棧(對于很多系統(tǒng)來說是典型的),那么4096個線程將會用盡所有地址空間,不再為代碼、靜態(tài)數(shù)據(jù)或者堆數(shù)據(jù)留有空間。雖然64位(或者更大)的系統(tǒng)不存在這種直接的地址空間限制,它們?nèi)匀恢痪邆溆邢薜馁Y源:如果你運(yùn)行太多的線程,最終會導(dǎo)致問題。盡管線程池(參見第9章)可以用來限制線程的數(shù)量,但這并不是靈丹妙藥,它們也有自己的問題。
如果客戶端/服務(wù)器應(yīng)用程序的服務(wù)器端為每一個鏈接啟動一個獨(dú)立的線程,對于少量的鏈接是可以正常工作的,但當(dāng)同樣的技術(shù)用于需要處理大量鏈接的高需求服務(wù)器時,就會因?yàn)閱犹嗑€程而迅速耗盡系統(tǒng)資源。在這種場景下,謹(jǐn)慎地使用線程池可以提供優(yōu)化的性能(參見第9章)。
最后,運(yùn)行越多的線程,操作系統(tǒng)就需要做越多的上下文切換。每個上下文切換都需要耗費(fèi)本可以花在有價(jià)值工作上的時間,所以在某些時候,增加一個額外的線程實(shí)際上會降低而不是提高應(yīng)用程序的整體性能。為此,如果你試圖得到系統(tǒng)的最佳性能,考慮可用的硬件并發(fā)(或缺乏之)并調(diào)整運(yùn)行線程的數(shù)量是必需的。
為了性能優(yōu)化而使用并發(fā)就像所有其他優(yōu)化策略一樣,它擁有極大提高應(yīng)用程序性能的潛力,但它也可能使代碼復(fù)雜化,使其更難理解和更容易出錯。因此,只有對應(yīng)用程序中的那些具有顯著增益潛力的性能關(guān)鍵部分才值得這樣做。當(dāng)然,如果性能收益的潛力僅次于設(shè)計(jì)清晰或關(guān)注點(diǎn)分離,可能也值得使用多線程設(shè)計(jì)。
假設(shè)你已經(jīng)決定確實(shí)要在應(yīng)用程序中使用并發(fā),無論是為了性能、關(guān)注點(diǎn)分離,或是因?yàn)椤岸嗑€程星期一”,對于C++程序員來說意味著什么?
本文節(jié)選自《C++并發(fā)編程實(shí)戰(zhàn)》
內(nèi)容簡介
書是一本基于C++11新標(biāo)準(zhǔn)的并發(fā)和多線程編程深度指南。內(nèi)容包括std::thread、std::mutex、std::future和std::async等基礎(chǔ)類的使用,內(nèi)存模型和原子操作、基于鎖和無鎖數(shù)據(jù)結(jié)構(gòu)的構(gòu)建,以及并行算法和線程管理,最后還介紹了多線程代碼的測試。本書的附錄部分還對C++11新語言特性中與多線程相關(guān)的項(xiàng)目進(jìn)行了簡要的介紹,并提供了C++11線程庫的完整參考。
本書適合于需要深入了解C++多線程開發(fā)的讀者,以及使用C++進(jìn)行各類軟件開發(fā)的開發(fā)人員、測試人員閱讀。使用第三方線程庫的讀者,也可以從本書后面的章節(jié)中了解到相關(guān)的指引和技巧。同時,本書還可以作為C++11線程庫的參考工具書。
本文轉(zhuǎn)載自異步社區(qū)。
原文鏈接:https://www.epubit.com/articleDetails?id=NC7E3EF91FDF00001D2E571D917303130
軟件開發(fā) 編程語言 c++
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。