有什么好的并发编程书籍推荐?还真有一本
今天小編要說的是《C++并發編程實戰》(第2版)這本書,很多程序員都知道這本書。第2版全新翻譯,給你一個不一樣的閱讀體驗。
《C++并發編程實戰》(第2版)由C++標準委員會成員編寫,囊括C++并發編程多個方面。作者Anthony Williams獨自起草并參與編寫了許多與多線程和并發相關的提案,這些提案塑造了C++標準的一部分。Anthony Williams持續參與了C++標準委員會并發小組的工作,包括對C++17標準進行改進,制定并發技術規約(Concurrency Technical Specification),以及編寫關于C++未來演化發展的提案等。
- 這是一本介紹C++并發和多線編程的深度指南,囊括了C++并發編程的多個方面,涉及啟動新線程以及設計全功能的多線程算法和數據結構等核心知識點;
- 本書譯文經過反復推敲,作譯者協同參與全書內容的翻譯和審讀,代碼配有詳細的中文注釋,內容簡潔易懂;
- 譯者還基于自己的開發經驗,補充了許多延伸知識點,適合想要深入了解C++多線程的開發人員深入學習;
- 本書提供強大的配套資源,包括近200頁的電子版附錄D以及140多份配套代碼文件。
本書內容是 C++新標準中涉及的并發與多線程功能,從std::thread、std::mutex和std::async的基本使用方法開始,一直到復雜的內存模型和原子操作。
假若讀者之前沒使用過C++11的新功能,那就需要先瀏覽一下附錄A,再開始閱讀正文,這將有助于透徹理解本書的代碼示例。正文中已經標注出用到C++新特性的地方,盡管如此,一旦你遇到任何從未見過的內容,也可以隨時翻查附錄。
如果讀者已經編寫過多線程代碼,并且經驗豐富,前幾章會讓你知曉已經熟知的工具與新標準的C++工具是怎樣對應的。倘若讀者要進行任何底層工作,涉及原子變量,則第5章必不可少。為了確保讀者真正熟知C++多線程編程的各種細節,例如異常安全(exception safety),那么,第8章值得好好學習。如果讀者肩負某種特定的編碼任務,索引和目錄會幫你迅速定位到有關章節。
即便你已經掌握了C++線程庫的使用方法,附錄D(可從異步社區下載)依然有用,例如可供你查閱各個類和函數調用的精準細節。你也可以考慮時不時地回顧一下主要章節,或強化記憶某個特定的模型,或重溫示例代碼。
簡要目錄
- 第1章 你好,C++并發世界
- 第2章 線程管控
- 第3章 在線程間共享數據
- 第4章 并發操作的同步
- 第5章 C++內存模型和原子操作
- 第6章 設計基于鎖的并發數據結構
- 第7章 設計無鎖數據結構
- 第8章 設計并發代碼
- 第9章 高級線程管理
- 第10章 并行算法函數
- 第11章 多線程應用的測試和除錯
樣章試讀:第1章 你好,C++并發世界
1.1 什么是并發
按最簡單、最基本的程度理解,并發(concurrency)是兩個或多個同時獨立進行的活動。并發現象遍布日常生活,我們時常接觸:我們可以邊走路邊說話;或者,左右手同時做出不一樣的動作;我們每個人也都可以獨立行事——當我游泳時,你可以觀看足球比賽;諸如此類。
1.1.1 計算機系統中的并發
若我們談及計算機系統中的并發,則是指同一個系統中,多個獨立活動同時進行,而非依次進行。這不足為奇。多年來,多任務操作系統可以憑借任務切換,讓同一臺計算機同時運行多個應用軟件,這早已稀松平常,而高端服務器配備了多處理器,實現了“真并發”(genuine concurrency)。大勢所趨,主流計算機現已能夠真真正正地并行處理多任務,而不再只是制造并發的表象。
很久之前,大多計算機都僅有一個處理器,處理器內只有單一處理單元或單個內核,許多臺式計算機至今依舊如此。這種計算機在同一時刻實質上只能處理一個任務,不過,每秒內,它可以在各個任務之間多次切換,先處理某任務的一小部分,接著切換任務,同樣只處理一小部分,然后對其他任務如法炮制。于是,看起來所有任務都正在同時執行。因此其被稱為任務切換。至此,我們談及的并發都基于這種模式。由于任務飛速切換,我們難以分辨處理器到底在哪一刻暫停某個任務而切換到另一個。任務切換對使用者和應用軟件自身都制造出并發的表象。由于是表象,因此對比真正的并發環境,當應用程序在進行任務切換的單一處理器環境下運行時,其行為可能稍微不同。具體而言,如果就內存模型(見第5章)做出不當假設,本來會導致某些問題,但這些問題在上述環境中卻有可能不會出現。第10章將對此深入討論。
多年來,配備了多處理器的計算機一直被用作服務器,它要承擔高性能的計算任務;現今,基于一芯多核處理器(簡稱多核處理器)的計算機日漸普及,多核處理器也用在臺式計算機上。無論是裝配多個處理器,還是單個多核處理器,或是多個多核處理器,這些計算機都能真正并行運作多個任務,我們稱之為硬件并發(hardware concurrency)。
圖1.1所示為理想化的情景。計算機有兩個任務要處理,將它們進行十等分。在雙核機(具有兩個處理核)上,兩個任務在各自的核上分別執行。另一臺單核機則切換任務,交替執行任務小段,但任務小段之間略有間隔。在圖1.1中,單核機的任務小段被灰色小條隔開,它們比雙核機的分隔條粗大。為了交替執行,每當系統從某一個任務切換到另一個時,就必須完成一次上下文切換(context switch),于是耗費了時間。若要完成一次上下文切換,則操作系統需保存當前任務的CPU狀態和指令指針[2],判定需要切換到哪個任務,并為之重新加載CPU狀態。接著,CPU有可能需要將新任務的指令和數據從內存加載到緩存,這或許會妨礙CPU,令其無法執行任何指令,加劇延遲。
圖1.1 兩種并發方式:雙核機上的并發執行與單核機上的任務切換
盡管多處理器或多核系統明顯更適合硬件并發,不過有些處理器也能在單核上執行多線程。真正需要注意的關鍵因素是硬件支持的線程數(hardware threads),也就是硬件自身真正支持同時運行的獨立任務的數量。即便是真正支持硬件并發的系統,任務的數量往往容易超過硬件本身可以并行處理的數量,因而在這種情形下任務切換依然有用。譬如,常見的臺式計算機能夠同時運行數百個任務,在后臺進行各種操作,表面上卻處于空閑狀態。正是由于任務切換,后臺任務才得以運作,才容許我們運行許多應用軟件,如文字處理軟件、編譯器、編輯軟件,以及瀏覽器等。圖1.2展示了雙核機上4個任務的相互切換,這同樣是理想化的情形,各個任務都被均勻切分。實踐中,許多問題會導致任務切分不均勻或調度不規則。我們將在第8章探究影響并發代碼性能的因素,將解決上述某些問題。
圖1.2 4個任務在雙核機上切換
本書涉及的技術、函數和類適用于各種環境:無論負責運行的計算機是配備了單核單處理器,還是多核多處理器;無論其并發功能如何實現,是憑借任務切換,還是真正的硬件并發,一概不影響使用。然而,也許讀者會想到,應用軟件如何充分利用并發功能,很大程度上取決于硬件所支持的并發任務數量。我們將在第8章講述設計并發的C++代碼的相關議題,也會涉及這點。
1.1.2 并發的方式
設想兩位開發者要共同開發一個軟件項目。假設他們處于兩間獨立的辦公室,而且各有一份參考手冊,則他們可以靜心工作,不會彼此干擾。但這令交流頗費周章:他們無法一轉身就與對方交談,遂不得不借助電話或郵件,或是需起身離座走到對方辦公室。另外,使用兩間辦公室有額外開支,還需購買多份參考手冊。
現在,如果安排兩位開發者共處一室,他們就能暢談軟件項目的設計,也便于在紙上或壁板上作圖,從而有助于交流設計的創意和理念。這樣,僅有一間辦公室要管理,并且各種資源通常只需一份就足夠了。但缺點是,他們恐怕難以集中精神,共享資源也可能出現問題。
這兩種安排開發者的辦法示意了并發的兩種基本方式。一位開發者代表一個線程,一間辦公室代表一個進程。第一種方式采用多個進程,各進程都只含單一線程,情況類似于每位開發者都有自己的辦公室;第二種方式只運行單一進程,內含多個線程,正如兩位開發者同處一間辦公室。我們可以隨意組合這兩種方式,掌控多個進程,其中有些進程包含多線程,有些進程只包含單一線程,但基本原理相同。接著,我們來簡略看看應用軟件中的這兩種并發方式。
1.多進程并發
在應用軟件內部,一種并發方式是,將一個應用軟件拆分成多個獨立進程同時運行,它們都只含單一線程,非常類似于同時運行瀏覽器和文字處理軟件。這些獨立進程可以通過所有常規的進程間通信途徑相互傳遞信息(信號、套接字、文件、管道等),如圖1.3所示。這種進程間通信普遍存在短處:或設置復雜,或速度慢,甚至二者兼有,因為操作系統往往要在進程之間提供大量防護措施,以免某進程意外改動另一個進程的數據;還有一個短處是運行多個進程的固定開銷大,進程的啟動花費時間,操作系統必須調配內部資源來管控進程,等等。
圖1.3 兩個進程并發運行并相互通信
進程間通信并非一無是處:通常,操作系統在進程間提供額外保護和高級通信機制。這就意味著,比起線程,采用進程更容易編寫出安全的并發代碼。某些編程環境以進程作為基本構建單元,其并發效果確實一流,譬如為Erlang編程語言準備的環境。
運用獨立的進程實現并發,還有一個額外優勢——通過網絡連接,獨立的進程能夠在不同的計算機上運行。這樣做雖然增加了通信開銷,可是只要系統設計精良,此法足以低廉而有效地增強并發力度,改進性能。
2.多線程并發
另一種并發方式是在單一進程內運行多線程。線程非常像輕量級進程:每個線程都獨立運行,并能各自執行不同的指令序列。不過,同一進程內的所有線程都共用相同的地址空間,且所有線程都能直接訪問大部分數據。全局變量依然全局可見,指向對象或數據的指針和引用能在線程間傳遞。盡管進程間共享內存通常可行,但這種做法設置復雜,往往難以駕馭,原因是同一數據的地址在不同進程中不一定相同。圖1.4展示了單一進程內的兩個線程借共享內存通信。
圖1.4 單一進程內的兩個線程借共享內存通信
我們可以啟用多個單線程的進程并在進程間通信,也可以在單一進程內發動多個線程而在線程間通信,后者的額外開銷更低。因此,即使共享內存帶來隱患,主流語言大都青睞以多線程的方式實現并發功能,當中也包括C++。再加上C++本身尚不直接支持進程間通信,所以采用多進程的應用軟件將不得不依賴于平臺專屬的應用程序接口(Application Program Interface,API)。鑒于此,本書專攻多線程并發,后文再提及并發,便假定采用多線程實現。
提到多線程代碼,還常常用到一個詞——并行。接下來,我們來厘清并發與并行的區別。
1.1.3 并發與并行
就多線程代碼而言,并發與并行(parallel)的含義很大程度上相互重疊。確實,在多數人看來,它們就是相同的。二者差別甚小,主要是著眼點和使用意圖不同。兩個術語都是指使用可調配的硬件資源同時運行多個任務,但并行更強調性能。當人們談及并行時,主要關心的是利用可調配的硬件資源提升大規模數據處理的性能;當談及并發時,主要關心的是分離關注點或響應能力。這兩個術語之間并非涇渭分明,它們之間仍有很大程度的重疊,知曉這點會對后文的討論有所幫助,兩者的范例將穿插本書。
至此,我們已明晰并發的含義,現在來看看應用軟件為什么要使用并發技術。
1.2 為什么使用并發技術
應用軟件使用并發技術的主要原因有兩個:分離關注點與性能提升。據我所知,實際上這幾乎是僅有的用到并發技術的原因。如果尋根究底,任何其他原因都能歸結為二者之一,也可能兼有(除非硬要說“因為我就是想并發”)。
1.2.1 為分離關注點而并發
一直以來,編寫軟件時,分離關注點(separation of concerns)幾乎總是不錯的構思:歸類相關代碼,隔離無關代碼,使程序更易于理解和測試,因此所含缺陷很可能更少。并發技術可以用于隔離不同領域的操作,即便這些不同領域的操作需同時進行;若不直接使用并發技術,我們將不得不編寫框架做任務切換,或者不得不在某個操作步驟中,頻繁調用無關領域的代碼。
考慮一個帶有用戶界面的應用軟件,需要由CPU密集處理,如臺式計算機上的DVD播放軟件。本質上,這個應用軟件肩負兩大職責:既要從碟片讀取數據,解碼聲音影像,并將其及時傳送給圖形硬件和音效硬件,讓DVD順暢放映,又要接收用戶的操作輸入,譬如用戶按“暫停”“返回選項單”“退出”等鍵。假若采取單一線程,則該應用軟件在播放過程中,不得不定時檢查用戶輸入,結果會混雜播放DVD的代碼與用戶界面的代碼。改用多線程就可以分離上述兩個關注點,一個線程只負責用戶界面管理,另一個線程只負責播放DVD,用戶界面的代碼和播放DVD的代碼遂可避免緊密糾纏。兩個線程之間還會保留必要的交互,例如按“暫停”鍵,不過這些交互僅僅與需要立即處理的事件直接關聯。
如果用戶發送了操作請求,而播放DVD線程正忙,無法馬上處理,那么在請求被傳送到該線程的同時,代碼通常能令用戶界面線程立刻做出響應,即便只是顯示光標或提示“請稍候”。這種方法使得應用軟件看起來響應及時。類似地,某些必須在后臺持續工作的任務,則常常交由獨立線程負責運行,例如,讓桌面搜索應用軟件監控文件系統變動。此法基本能大幅簡化各線程的內部邏輯,原因是線程間交互得以限定于代碼中可明確辨識的切入點,而無須將不同任務的邏輯交錯散置。
這樣,線程的實際數量便與CPU既有的內核數量無關,因為用線程分離關注點的依據是設計理念,不以增加運算吞吐量為目的。
1.2.2 為性能而并發:任務并行和數據并行
多處理器系統已存在數十年,不過一直以來它們大都只見于巨型計算機、大型計算機和大型服務器系統。但是,芯片廠家日益傾向設計多核芯片,在單一芯片上集成2個、4個、16個或更多處理器,從而使其性能優于單核芯片。于是,多核臺式計算機日漸流行,甚至多核嵌入式設備亦然。不斷增強的算力并非得益于單個任務的加速運行,而是來自多任務并行運作。從前,處理器更新換代,程序自然而然隨之加速,程序員可以“坐享其成,不勞而獲”。但現在,正如Herb Sutter指出的“免費午餐沒有了![3]”,軟件若要利用增強的這部分算力,就必須設計成并發運行任務。所以程序員必須警覺,特別是那些躊躇不前、忽視并發技術的同業,有必要注意熟練掌握并發技術,儲備技能。
增強性能的并發方式有兩種。第一種,最直觀地,將單一任務分解成多個部分,各自并行運作,從而節省總運行耗時。此方式即為任務并行。盡管聽起來淺白、直接,但這卻有可能涉及相當復雜的處理過程,因為任務各部分之間也許存在紛繁的依賴。任務分解可以針對處理過程,調度某線程運行同一算法的某部分,另一線程則運行其他部分;也可以針對數據,線程分別對數據的不同部分執行同樣的操作,這被稱為數據并行。
易于采用上述并行方式的算法常常被稱為尷尬并行[4]算法。其含義是,將算法的代碼并行化實在簡單,甚至簡單得會讓我們尷尬,實際上這是好事。我還遇見過用其他術語描述這類算法,叫“天然并行”(naturally parallel)與“方便并發”(conveniently concurrent)。尷尬并行算法具備的優良特性是可按規模伸縮——只要硬件支持的線程數目增加,算法的并行程度就能相應提升。這種算法是成語“眾擎易舉”的完美體現。算法中除尷尬并行以外的部分,可以另外劃分成一類,其并行任務的數目固定(所以不可按規模伸縮)。第8章和第10章將涵蓋按線程分解任務的方法。
第二種增強性能的并發方式是利用并行資源解決規模更大的問題。例如,只要條件適合,便同時處理2個文件,或者10個,甚至20個,而不是每次1個。同時對多組數據執行一樣的操作,實際上是采用了數據并行,其著眼點有別于任務并行。采用這種方式處理單一數據所需的時間依舊不變,而同等時間內能處理的數據相對更多。這種方式明顯存在局限,雖然并非任何情形都會因此受益,但數據吞吐量卻有所增加,進而帶來突破。例如,若能并行處理視頻影像中不同的區域,就會提升視頻處理的解析度。
1.2.3 什么時候避免并發
知道何時避免并發,與知道何時采用并發同等重要。歸根結底,不用并發技術的唯一原因是收益不及代價。多數情況下,采用了并發技術的代碼更難理解,編寫和維護多線程代碼會更勞心費神,并且復雜度增加可能導致更多錯誤。編寫正確運行的多線程代碼需要額外的開發時間和相關維護成本,除非潛在的性能提升或分離關注點而提高的清晰度值得這些開銷,否則別使用并發技術。
此外,性能增幅可能不如預期。線程的啟動存在固有開銷,因為系統須妥善分配相關的內核資源和棧空間,然后才可以往調度器添加新線程,這些都會耗費時間。假如子線程上運行的任務太快完成,處理任務本身的時間就會遠短于線程啟動的時間,結果,應用程序的整體性能很可能還不如完全由主線程直接執行任務的性能。
再者,線程是一種有限的資源。若一次運行太多線程,便會消耗操作系統資源,可能令系統整體變慢。而且,由于每個線程都需要獨立的棧空間[5],如果線程太多,就可能耗盡所屬進程的可用內存或地址空間。在采用扁平模式內存架構的32位進程中,可用的地址空間是4GB,這很成問題:假定每個線程棧的大小都是1MB(這個大小常見于許多系統),那么4096個線程即會把全部地址空間耗盡,使得代碼、靜態數據和堆數據無地立足。盡管64位系統(或指令集寬度更大的系統)對地址空間的直接限制相對寬松,但其資源依舊有限,運行太多線程仍將帶來問題。雖然線程池可用于控制線程數量(見第9章),但也非萬能妙法,它自身也有局限。
假設,在服務器端,客戶端/服務器(Client/Server,C/S)模式的應用程序為每個連接發起一個獨立的線程。如果只有少量連接,這尚能良好工作。不過,請求量巨大的服務器需要處理的連接數目龐大,若采用同樣的方法,就會發起過多線程而很快耗盡系統資源。針對這一情形,如果要達到最優性能,便須謹慎使用線程池(見第9章)。
最后,運行的線程越多,操作系統所做的上下文切換就越頻繁,每一次切換都會減少本該用于實質工作的時間。結果,當線程數目達到一定程度時,再增加新線程只會降低應用軟件的整體性能,而不會提升性能。正因如此,若讀者意在追求最優系統性能,則須以可用的硬件并發資源為依據(或反之考慮其匱乏程度),調整運行線程的數目。
為了提升性能而使用并發技術,與其他優化策略相仿:它極具提升應用程序性能的潛力,卻也可能令代碼復雜化,使之更難理解、更容易出錯。所以,對于應用程序中涉及性能的關鍵部分,若其具備提升性能的潛力,收效可觀,才值得為之實現并發功能。當然,如果首要目標是設計得清楚明晰或分離關注點,而提升性能居次,也值得采用多線程設計。
倘若讀者已決意在應用軟件中使用并發技術,不論是為了性能,還是為了分離關注點,或是因為“多線程的良辰吉日已到”,那么這對C++程序員意義何在?
1.3 并發與C++多線程
以標準化形式借多線程支持并發是C++的新特性。C++11標準發布后,我們才不再依靠平臺專屬的擴展,可以用原生C++直接編寫多線程代碼。標準C++線程庫的成型歷經種種取舍,若要掌握其設計邏輯,則知曉其歷史淵源頗為重要。
1.3.1 C++多線程簡史
1998年發布的C++標準并沒有采納線程,許多語言要素在設定其操作效果之時,考慮的依據是抽象的串行計算機(sequential abstract machine)[6]。不僅如此,內存模型亦未正式定義,若不依靠編譯器相關的擴展填補C++98標準的不足,就無法寫出多線程程序。
為了支持多線程,編譯器廠商便自行對C++進行擴展;廣泛流行的C語言的多線程API,如符合POSIX規范的C語言多線程接口[7]和微軟Windows系統的多線程API,使得許多C++廠商借助各種平臺專屬的擴展來支持多線程。這種來自編譯器的支持普遍受限,在特定平臺上只能使用相應的C語言API,并且須確保C++運行庫(譬如異常處理機制的代碼)在多線程場景下可以正常工作。盡管甚少編譯器廠商給出了正規的多線程內存模型,但編譯器和處理器運作優良,使數量龐大的多線程程序得以用C++寫就。
C++程序員并不滿足于使用平臺專屬的C語言API處理多線程,他們更期待C++類庫提供面向對象的多線程工具。應用程序框架(如微軟基礎類庫[8])和通用的C++程序庫(如Boost和自適配通信環境[9])已累積開發了多個系列的C++類,封裝了平臺專屬的底層API,并提供高級的多線程工具以簡化編程任務。盡管C++類庫的具體細節千差萬別,特別是在啟動新線程這一方面,但這些C++類的總體特征有很多共同之處。例如,通過資源獲取即初始化(Resource Acquisition Is Initialization,RAII)的慣用手法進行鎖操作,它確保了一旦脫離相關作用域,被鎖的互斥就自行解開。這項設計特別重要,為許多C++類庫所共有,使程序員受益良多。
現有的C++編譯器在許多情況下都能支持多線程,再結合平臺專屬的API以及平臺無關的類庫,如Boost和ACE,為編寫多線程的C++代碼奠定了堅實的基礎。于是,無數多線程應用的組件由C++寫成,代碼量龐大,以百萬行計。不過它們缺乏統一標準的支持,這意味著,由于欠缺多線程內存模型,因此在某些情形下程序會出現問題,下面兩種情形尤甚:依賴某特定的處理器硬件架構來獲得性能提升,或是編寫跨平臺代碼,但編譯器的行為卻因平臺而異。
1.3.2 新標準對并發的支持
隨著C++11標準的發布,上述種種弊端被一掃而空。C++標準庫不僅規定了內存模型,可以區分不同線程,還擴增了新類,分別用于線程管控(見第2章)、保護共享數據(見第3章)、同步線程間操作(見第4章)以及底層原子操作(見第5章)等。
前文提及的幾個C++類庫在過往被使用過程中積累了很多經驗,C++11線程庫對它們頗為倚重。具體而言,新的C++庫以Boost線程庫作為原始范本,其中很多類在Boost線程庫中存在對應者,名字和結構均一致。另外,Boost線程庫自身做了多方面改動,以遵循C++標準。因此,原來的Boost使用者應該會對標準C++線程庫深感熟悉。
正如本章開篇所述,C++11標準進行了多項革新,支持并發特性只是其中之一,語言自身還有很多的改進,以便程序員揮灑自如。雖然這些改進普遍超出本書范圍,但其中一部分直接影響了C++線程庫本身及其使用方式。附錄A將簡要介紹這些C++新特性。
1.3.3 C++14和C++17進一步支持并發和并行
C++14進一步增添了對并發和并行的支持,具體而言,是引入了一種用于保護共享數據的新互斥(見第3章)。C++17則增添了一系列適合新手的并行算法函數(見第10章)。這兩版標準都強化了C++的核心和標準程序庫的其他部分,簡化了多線程代碼的編寫。
如前文所述,C++標準委員會還發布了并發技術規約,詳述了對C++標準提供的類和函數所做的擴展,特別是有關線程間的同步操作。
C++明確規定了原子操作的語義,并予以直接支持,使開發者得以擺脫平臺專屬的匯編語言,僅用純C++即可編寫出高效的代碼。對于力求編寫高效且可移植的代碼的開發者,這簡直如有神助:不但由編譯器負責處理平臺的底層細節,還能通過優化器把操作語義也考慮在內,兩者聯手改進性能,使程序的整體優化效果更上一層樓。
1.3.4 標準C++線程庫的效率
通常,對于從事高性能計算工作的開發者,無論是從整體上考量C++,還是就封裝了底層工具的C++類而言(具體來說,以新的標準C++線程庫中的類為例),他們最在意的因素通常是運行效率。若要實現某項功能,代碼可以借助高級工具,或者直接使用底層工具。兩種方式的運行開銷不同,該項差異叫作抽象損失[10]。如果讀者追求極致性能,清楚這點便尤為重要。
在設計C++標準庫和標準C++線程庫時,C++標準委員會對此非常注意。其中一項設計目標是,假定某些代碼采用了C++標準庫所提供的工具,如果改換為直接使用底層API,應該不會帶來性能增益,或者收效甚微。因此,在絕大多數主流平臺上,C++標準庫得以高效地實現(低抽象損失)。
總有開發者追求性能極限,恨不得下探最底層,親手掌控半導體器件,以窮盡計算機的算力。C++標準委員會的另一個目標是,確保C++提供充足的底層工具來滿足需求。為此,新標準帶來了新的內存模型,以及全方位的原子操作庫,其能直接單獨操作每個位、每個字節,還能直接管控線程同步,并讓線程之間可以看見數據變更。過去,開發者若想深入底層,就得選用平臺專屬的匯編語言;現在,在許多場合,這些原子型別和對應的操作都足以取而代之。只要代碼采用了新標準的數據型別與操作,便更具可移植性,且更容易維護。
C++標準庫還提供了高級工具,抽象程度更高,更易于編寫多線程代碼,出錯機會更少。使用這些工具必須執行額外的代碼[11],所以有時確實會增加性能開銷,但這種性能開銷不一定會引發更多抽象損失。與之相比,實現同樣的功能,手動編寫代碼所產生的開銷往往更高。此外,對于上述絕大部分額外的代碼,編譯器會妥善進行內聯。
針對某種特定的使用需求,一些高級工具提供了所需功能,有時還給出了額外功能,超出了原本的需求。在大多情況下,這都不成問題:未被使用的功能完全不產生開銷。只有在極少數情況下,這些未被使用的功能會影響其他代碼的性能。若讀者追求卓越性能,無奈高級工具的開銷過大,那最好還是利用底層工具親自編寫所需功能。在絕大部分情況中,這將導致復雜度和出錯的可能性同時大增,而性能提升卻十分有限,得益遠遠不償所失。有時候,即便性能剖析表明瓶頸在于C++標準庫的工具,但根本原因還是應用程序設計失當,而非類庫的實現欠佳。譬如,如果太多線程爭搶同一個互斥對象,就會嚴重影響性能。與其試圖壓縮互斥操作以節省瑣碎時間,不如重新構建應用程序,從根本上減少對互斥對象的爭搶,收效很可能更明顯。第8章會涵蓋上述議題:如何設計并發應用,減少資源爭搶。
C++標準庫還是有可能無法達到性能要求,無法提供所需的功能,但這種情況非常少,一旦出現這種情況,就似乎有必要使用平臺專屬的工具了。
1.3.5 平臺專屬的工具
雖然標準C++線程庫給出了相當全面的多線程和并發工具,但在任何特定的平臺上,總有平臺專屬的工具超額提供標準庫之外的功能。為了可以便捷利用這些工具,同時又能照常使用標準C++線程庫,C++線程庫的某些型別有可能提供成員函數native_handle(),允許它的底層直接運用平臺專屬的API。因其本質使然,任何采用native_handle()的操作都完全依賴于特定平臺,這也超出了本書的范圍(以及C++標準庫自身的范圍)。
總結
以上是生活随笔為你收集整理的有什么好的并发编程书籍推荐?还真有一本的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 解决迅雷、酷我软件安装乱码问题
- 下一篇: Mac OS 使用远程桌面登录服务器