內存模型一:什麼是內存模型

縱然工做再忙也應該要留下本身思考的時間,此次我總結了一下對於內存模型的理解,原由是在公司聽了一場關於多線程編程的分享會。首先解釋一下,內存模型和對象模型是不一樣的。對象模型說的是一個對象是如何被設計的,其在內存中是如何佈局的。而內存模型說的是,在多核多線程環境下,各類不一樣的CPU是如何以一種統一的方式來與內存交互的。程序員

 

背景知識:CPU的高速緩存編程

總所周知,CPU和內存並非直接交換數據的,它們之間還隔着一個高速緩存。高速緩存是對程序員透明的,這意味在編程的時候是感知不到CPU的緩存的存在的。通常狀況下確實如此,但在,在某些特殊的情形下(多核多線程),就不能忽略緩存的存在了。這實際上是和緩存的設計有關係,通常多處理器下的每一個CPU都有一個本身的緩存,存儲在這個緩存的數據是其它CPU是沒法查看的。緩存

 

引入問題1:內存可見性安全

問題來了,因爲緩存是每一個CPU私有的,那麼在多線程環境下,某個CPU修改了變量x後保存在本地緩存,對於其它CPU,什麼時候才能發現變量x被修改呢?如何保證其它CPU的緩存中持有的x的值是最新的呢?多線程

因而可知,在多核多線程環境下,讀寫共享變量要解決的不只是原子性,還須要保證其內存可見性。更糟的是,現代CPU一般在執行指令時會容許必定程度上的亂序,這使保證在多個CPU緩存的數據一致更是增長了複雜性。一般方法是經過一個協議來保證數據在各個CPU的緩存是一致性,這就是緩存一致性協議。編程語言

關於緩存一致性簡單的舉個列子。CPU-0嘗試STORE(更新)變量x,但其發現其它CPU的緩存也持有這個x的copy(x此時爲Shared狀態,非單個CPU獨佔),那麼當CPU-0在STORE以前,必須經過一個disable消息,告訴其它CPU所持有的變量x已經爲髒數據,是不可用狀態。其它CPU在收到這個disable消息後必須迴應CPU-0一個ack消息,這時候CPU-0才能開始STORE變量x。佈局

經過緩存一致性協議以後,內存可見性問題彷佛是得以解決了。可是,這裏面還隱藏着另一個問題:指令亂序!優化

 

引入問題2:亂序(memory reorder)線程

先來解釋一下,亂序,指的是程序指令實際上執行的順序,和咱們書寫的指令的順序不一致。亂序分兩種,分別是編譯器的指令重排和CPU的亂序執行。本意上亂序是爲了優化指令執行的速度而產生的。而且爲了維護程序原來的語義,編譯器和CPU不會對兩個有數據依賴的指令重排(reorder)。這種保護在單線程的環境下是能夠工做的,可是到了多線程,問題就複雜了。設計

 

舉個例子,CPU-0將要執行兩條指令,分別是:

  1. STORE x
  2. LOAD y

當CPU-0執行指令1的時候,發現這個變量x的當前狀態爲Shared,這意味着其它CPU也持有了x,所以根據緩存一致性協議,CPU-0在修改x以前必須通知其它CPU,直到收到來自其它CPU的ack纔會執行真正的修改x。可是,事情沒有這麼簡單。現代CPU緩存一般都有一個Store Buffer,其存在的目的是,先將要Store的變量記下來,注意此時並不真的執行Store操做,而後待時機合適的時候再執行實際的Store。有了這個Store Buffer,CPU-0在向其它CPU發出disable消息以後並非乾等着,而是轉而執行指令2(因爲指令1和指令2在CPU-0看來並不存在數據依賴)。這樣作效率是有了,可是也帶來了問題。雖然咱們在寫程序的時候,是先STORE x再執行LOAD y,可是實際上CPU倒是先LOAD y再STORE x,這個即是CPU亂序執行(reorder)的一種狀況!

當你的程序要求指令一、2有邏輯上的前後順序時,CPU這樣的優化就是有問題的。可是,CPU並不知道指令之間蘊含着什麼樣的邏輯順序,在你告訴它以前,它只是假設指令之間都沒有邏輯關聯,而且盡最大的努力優化執行速度。所以咱們須要一種機制能告訴CPU:這段指令執行的順序是不可被重排的!作這種事的就是內存屏障(memory barrier)!

 

內存屏障

仍是上面那個例子,若是不想指令一、2被CPU重排,程序應該這麼寫:

  1. STORE x
  2. WMB (Write memory barrier)
  3. LOAD y

經過在STORE x以後加上這個寫內存屏障,就能保證在以後LOAD y指令不會被重排到STORE x以前了。

 

內存模型是什麼

前面講了那麼多,那麼內存模型是什麼呢?

首先,殘酷的現實就是每一個CPU設計都是不一樣的,每一個CPU對指令亂序的程度也是不同的。比較保守的如x86僅會對Store Load亂序,可是一些優化激進的CPU(PS的Power)會容許更多狀況的亂序產生。若是目標是寫一個跨平臺多線程的程序,那麼勢必要了解每個CPU的細節,來插入確切的、足夠的內存屏障來保證程序的正確性。這是多麼的不科學啊!科學的作法應該是,我爲一個抽象的機器寫一套抽象的程序,而後在不一樣的平臺下讓編程語言、編譯器來生成合適的內存屏障。所以,咱們有了內存模型的概念。不一樣平臺下的實現差異被統一的內存模型所隱藏,只須要根據這個抽象的內存模型來編寫程序便可,這即是偉大的抽象...

所以,在C++11裏有了內存模型的在以後,咱們能夠僅經過標準庫就實現出跨平臺線程安全的lock free程序(這在C++11以前是作不到的,雖然Java早就有了內存模型)。

 

參考資料

  1. memory barriers a hardware view for software hackers
相關文章
相關標籤/搜索