[譯]如何設計多核計算機以正確執行多進程程序

論文原題:How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programsbash

摘要 - 不少大型的有序計算機不必定是按照程序指定的順序來執行的。一次執行,若是其結果和按順序執行的結果一致,則是一次正確的執行。不過,對於一個多核處理器,每一個核都正確執行,卻不必定能保證整個程序是正確執行的。要保證計算機正確的處理多進程程序,還須要知足一些額外的條件。併發

一個高速運行的處理器不必定按照程序指定的順序執行。一個處理器必須知足下面的條件,才能保證正確的執行:處理器的執行結果,必須和按照程序指定順序執行的結果一致。性能

倒不如直接說,處理器必須按照程序指定的順序執行。或許理論上還有不按順序執行,也能保證正確結果的方法,不過這種方法豈不難以解釋fetch

一個知足該條件的處理器就能夠被稱做是有序的(sequential)。而對於多個處理器訪問同一塊內存的場景,則必須知足下面的條件才能保證多進程併發的正確性:spa

  • 任何一次執行的結果,和全部處理器按照某個順序執行的結果一致;
  • 在這」某個順序執行「中單獨看每一個處理器,則每一個處理器也都是按照程序指定的順序執行的。

一個多核處理器若是知足了這樣的條件,就被稱做是順序一致的(sequentially consistent)設計

僅僅是每一個處理器保證本身的有序性,是沒法保證最終多核計算機的順序一致的。咱們在這裏介紹一種有序處理器和內存模塊協做的方法,以確保最終的多核處理器是順序一致的。code

咱們假設計算機由一組處理器和一組內存模塊構成,而處理器之間只經過內存模塊進行交互(任何特殊的寄存器也當作是內存模塊)。那麼咱們惟一須要關心的處理器操做就是往內存發起「讀」或「寫」的請求。咱們假設每一個處理器都會發起一系列的讀寫請求(有時候,處理器得等當前請求執行完後,才能繼續下一步工做,但咱們不用關心這個)。隊列

爲了闡述這個問題,咱們來考慮一個簡單的兩進程互斥協議。每一個進程都有一個臨界區,協議的目的是保證任什麼時候候只有一個進程可以在臨界區內執行。協議設計以下:進程

process1
  a := 1;
  if b = 0 then critical section;
    a := 0
  else ··· 
  fi
複製代碼
process2
  b := 1;
  if a = 0 then critical section;
    b := 0
  else ··· 
  fi
複製代碼

else裏的內容保證進程最終可以進入臨界區(譯者注:也就是不會死鎖吧),可是這和咱們須要討論的問題無關。當這個程序在順序一致的多核計算機上執行時,兩個進程是不能同時在臨界區內執行的。事件

咱們首先注意到,一個有序處理器可能以任意的順序執行「b:=1"和process1中的"fetch b"(當只考慮process1時,這兩個操做的順序如何卻是沒什麼關係)。不過,顯而易見,若是先執行"fetch b"會致使兩個進程能同時進入臨界區執行。這引出了咱們對於多核計算機的第一個必要條件:

必要條件R1:每一個處理器必須按照程序指定的順序處理內存請求。

要知足必要條件R1其實是個複雜的問題。好比有時一個值要計算好了,才能寫到內存,這樣寫內存的耗時會比較長。而處理器每每能夠很快發起讀內存的請求,此時,上一次寫內存的請求卻不必定完成了。爲了最小化等待時間,處理器能夠先向內存發起寫請求,而不攜帶具體要寫的值。固然,內存要等接受到這個具體的值,才能完成最終的寫操做。

舉個例子

a = a + 1
get b
複製代碼

對於上面的程序,處理器能夠採用兩種方式:

  1. 處理器先計算出 a + 1,再將寫a的請求,以及計算出的a的新值一塊兒發給內存。以後處理器再向內存發起讀b的請求;
  2. 先對內存發起寫a的請求,可是不攜帶要寫的值,以後,便可發起讀b的請求。處理器可在以後的某個時間,計算出a + 1來,發給內存,以完成a = a + 1。

感受這種討論蠻奇怪的。

必要條件R1並不足以保證正確的執行。咱們假設每一個內存模塊都有好幾個端口,每一個端口服務於一個處理器(或是I/O通道)。咱們讓 a 和 b 的值存儲在不一樣的內存模塊中,而且考慮按以下順序發生的事件:

  1. 處理器1發送 a := 1 的請求給內存模塊1,內存模塊1正忙着其餘事;
  2. 處理器1發送 fetch b 的請求給內存模塊2,內存模塊2閒着,當即開始處理此次請求;
  3. 處理器2發送 b := 1 的請求給內存模塊2,這個請求得等處理器1的 fetch b 完成了才能執行;
  4. 處理器2發送 fetch a 的請求給內存模塊1,內存模塊1還沒忙完。

這個時候,有兩個操做掛在內存模塊2上等待執行。若是處理器2的 fetch a 先執行了,那麼上文的那個互斥協議就出問題了,由於兩個process就同時進入臨界區了。這在內存模塊採起輪詢的調度機制來處理請求時,是有可能發生的。

在這種狀況下,由於到達內存模塊1的兩次請求,並未按照他們被接收到的順序執行,而致使了錯誤。這引出了下面的必要條件。

必要條件R2: 一個獨立的內存模塊在服務全部處理器的請求時,必須基於FIFO隊列。發起內存請求,便是將請求加入到FIFO隊列。

而必要條件R1則意味着處理器在發起更多的內存請求以前,都必須等待當前的請求入隊。所以,若是隊列滿了,處理器就得等着。若是多個處理器同時嘗試將請求入隊,那麼誰先入隊就不要緊了。

注意,若是一次讀請求A,讀的那塊內存,正好寫請求B想要改,而且寫請求B已經在隊列裏了,那麼讀請求A就不須要入隊了,直接返回隊列中寫請求B所要寫的值就好了。若是隊列中有多個這樣的寫請求,返回最新入隊的那個寫請求所要寫的值就好了。

必要條件R1和R2保證了若是一個核是有序的,那麼多核處理器就是順序一致性的。爲了證實這點,咱們引入了關係符號 "->" 來描述內存請求。 咱們定義 "A -> B" 當且僅當:

  1. A和B是被同一個處理器發起的請求,且A發起在B以前;
  2. A和B請求的是同一個內存模塊,而且A在B以前入隊。

能夠很容易看出來,必要條件R1和R2意味着在內存請求上存在着 "->" 這樣一個偏序關係。利用處理器的有序性,咱們能夠證實下面這個結論:對同一個值進行的內存讀寫操做就好像這些操做都是按照某種順序執行的,所以 A->B 也就意味着A是在B以前執行的。這反過來就證實了多核計算機的順序一致性。

感受這段證實的意思就是:R1和R2保證了內存請求上的偏序關係"->",而該偏序關係保證了多核計算機的順序一致性。(emmmmm,有點謎...)

必要條件R2要求內存模塊必須用FIFO的策略來響應請求,這就意味着若是隊列頭是一個寫請求,而且須要寫的值還沒收到,那麼內存模塊就須要等待,就會處於空閒狀態。咱們能夠弱化R2,來容許內存在這種狀況下能夠響應其餘請求。咱們只須要要求針對同一塊內存單元的全部請求必須按照FIFO的順序響應既能夠了。對不一樣內存單元的請求能夠是亂序響應的。順序一致性依然是可以獲得保證的,由於這樣其實和將每個內存單元都當作一個擁有本身隊列的獨立內存模塊沒啥區別。(現實中,這些模塊可能會因硬件特質不一樣,而有不一樣的響應速度和隊列容量,但這並不影響順序一致性)。

爲了保證順序一致性,須要捨棄一些可以提高有序處理器性能的技術。對於一些應用來講,犧牲性能而追求順序一致性多是不值得的。此時,咱們必須意識到按照常規的多核程序設計方法設計出來的程序就不必定能正確執行了。此時,咱們必須在機器指令層面設計其餘的協議來保證多核之間的同步,可這樣,正確性的驗證也會變得很是繁雜。

對於順序一致性,個人理解就是,多核計算機必須按照程序指定的順序執行操做,而內存必須按照處理器發起請求的順序處理讀寫(內存也等於間接的按照程序指定的順序執行操做了)。 那麼順序一致性的主要用途就是,它保證多核系統的運做是按照程序指定的順序來的,這樣咱們才能無後顧之憂的設計咱們的程序。

相關文章
相關標籤/搜索