亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

C ++ 11引入了標準化的內存模型。這是什么意思?它將如何影響C ++編程?

C ++ 11引入了標準化的內存模型。這是什么意思?它將如何影響C ++編程?

C++
慕慕森 2019-08-05 15:56:26
C ++ 11引入了標準化的內存模型。這是什么意思?它將如何影響C ++編程?C ++ 11引入了標準化的內存模型,但究竟是什么意思呢?它將如何影響C ++編程?這篇文章(引用Herb Sutter的Gavin Clarke)說,內存模型意味著C ++代碼現在有一個標準化的庫可以調用,無論是誰編譯器以及它運行的平臺。有一種標準方法可以控制不同線程與處理器內存的對話方式?!爱斈阏務撛跇藴手械牟煌瑑群酥g分割[代碼]時,我們正在談論內存模型。我們將優化它,而不會破壞人們將在代碼中做出的以下假設,” Sutter說。好吧,我可以在網上記住這個和類似的段落(因為我從出生以來就擁有自己的記憶模型:P),甚至可以發布作為其他人提出的問題的答案,但說實話,我并不完全明白這個。C ++程序員以前用于開發多線程應用程序,那么如果它是POSIX線程,Windows線程或C ++ 11線程,它又如何重要呢?有什么好處?我想了解低級細節。我也覺得C ++ 11內存模型與C ++ 11多線程支持有某種關系,因為我經常將這兩者結合在一起。如果是,究竟是怎么回事?他們為什么要相關?由于我不知道多線程的內部工作原理以及內存模型的含義,請幫助我理解這些概念。:-)
查看完整描述

3 回答

?
慕勒3428872

TA貢獻1848條經驗 獲得超6個贊

首先,你必須學會像語言律師那樣思考。

C ++規范不引用任何特定的編譯器,操作系統或CPU。它引用了一個抽象機器,它是實際系統的概括。在語言律師的世界里,程序員的工作就是為抽象機器編寫代碼; 編譯器的工作是在具體機器上實現該代碼。通過嚴格按照規范進行編碼,無論是今天還是50年后,您都可以確定您的代碼無需在具有兼容C ++編譯器的任何系統上進行編譯和運行。

C ++ 98 / C ++ 03規范中的抽象機器基本上是單線程的。所以不可能編寫相對于規范“完全可移植”的多線程C ++代碼。該規范甚至沒有說明內存加載和存儲的原子性或加載和存儲可能發生的順序,更不用說像互斥體這樣的東西了。

當然,您可以在實踐中為特定的具體系統編寫多線程代碼 - 例如pthreads或Windows。但是沒有標準的方法來為C ++ 98 / C ++ 03編寫多線程代碼。

C ++ 11中的抽象機器是設計多線程的。它還有一個定義明確的內存模型 ; 也就是說,它說明了在訪問內存時編譯器可能會做什么,也可能不會做什么。

請考慮以下示例,其中兩個線程同時訪問一對全局變量:

           Global
           int x, y;Thread 1            Thread 2x = 17;             cout << y << " ";y = 37;             cout << x << endl;

線程2可能輸出什么?

在C ++ 98 / C ++ 03下,這甚至不是Undefined Behavior; 問題本身毫無意義,因為標準沒有考慮任何稱為“線程”的東西。

在C ++ 11下,結果是Undefined Behavior,因為加載和存儲通常不需要是原子的。這可能看起來不是很大的改善......而且它本身并非如此。

但是使用C ++ 11,你可以這樣寫:

           Global
           atomic<int> x, y;Thread 1                 Thread 2x.store(17);             cout << y.load() << " ";y.store(37);             cout << x.load() << endl;

現在情況變得更有趣了。首先,定義了此處的行為。線程2現在可以打印0 0(如果它在線程1之前運行),37 17(如果它在線程1之后運行),或者0 17(如果它在線程1分配給x但在它分配給y之前運行)。

它無法打印的是37 0,因為C ++ 11中原子加載/存儲的默認模式是強制執行順序一致性。這只意味著所有加載和存儲必須“好像”它們按照您在每個線程中編寫它們的順序發生,而線程之間的操作可以交錯,但系統喜歡。因此,atomics的默認行為為加載和存儲提供了原子性排序。

現在,在現代CPU上,確保順序一致性可能很昂貴。特別是,編譯器可能會在每次訪問之間發出完整的內存屏障。但是,如果您的算法可以容忍無序的加載和存儲; 即,如果它需要原子性而不是訂購; 即,如果它可以容忍37 0這個程序的輸出,那么你可以這樣寫:

           Global
           atomic<int> x, y;Thread 1                            Thread 2x.store(17,memory_order_relaxed);   cout << y.load(memory_order_relaxed) << " ";y.store(37,memory_order_relaxed);   cout << x.load(memory_order_relaxed) << endl;

CPU越現代,就越有可能比前一個例子更快。

最后,如果您只需要按順序保持特定的加載和存儲,您可以編寫:

           Global
           atomic<int> x, y;Thread 1                            Thread 2x.store(17,memory_order_release);   cout << y.load(memory_order_acquire) << " ";y.store(37,memory_order_release);   cout << x.load(memory_order_acquire) << endl;

這將我們帶回有序的加載和存儲 - 因此37 0不再是可能的輸出 - 但它以最小的開銷實現了這一點。(在這個簡單的例子中,結果與完整的順序一致性相同;在較大的程序中,它不會。)

當然,如果您想要查看的唯一輸出是0 0或者37 17,您可以在原始代碼周圍包裝互斥鎖。但是如果你已經讀過這篇文章了,我打賭你已經知道它是如何工作的,這個答案已經比我預想的要長:-)。

所以,底線?;コ怏w很棒,C ++ 11將它們標準化。但有時出于性能原因,您需要較低級別的基元(例如,經典的雙重檢查鎖定模式)。新標準提供了高級小工具,如互斥鎖和條件變量,它還提供低級小工具,如原子類型和各種內存屏障。因此,現在您可以完全使用標準指定的語言編寫復雜的高性能并發例程,并且可以確定您的代碼將在今天的系統和未來的系統上編譯和運行。

雖然坦率地說,除非您是專家并且正在處理一些嚴重的低級代碼,否則您應該堅持使用互斥鎖和條件變量。這就是我打算做的事情。

有關這些內容的更多信息,請參閱此博客文章。


查看完整回答
反對 回復 2019-08-05
?
長風秋雁

TA貢獻1757條經驗 獲得超7個贊

這是一個多年前的問題,但是非常受歡迎,值得一提的是學習C ++ 11內存模型的絕佳資源。我認為總結他的演講是沒有意義的,以便再做一個完整的答案,但鑒于這是實際編寫標準的人,我認為值得觀看談話。

Herb Sutter有一個長達3個小時的關于C ++ 11內存模型的討論,名為“atomic <> Weapons”,可在Channel9網站上找到 - 第1 部分第2部分。這個講座非常技術性,涵蓋以下主題:

  1. 優化,種族和記憶模型

  2. 訂購 - 什么:獲取和發布

  3. 訂購 - 如何:互斥鎖,原子和/或柵欄

  4. 編譯器和硬件的其他限制

  5. 代碼和性能:x86 / x64,IA64,POWER,ARM

  6. 輕松的原子論

談話沒有詳細說明API,而是關于推理,背景,幕后和幕后(您是否知道輕松的語義被添加到標準中只是因為POWER和ARM不能有效地支持同步加載?)。


查看完整回答
反對 回復 2019-08-05
  • 3 回答
  • 0 關注
  • 652 瀏覽

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號