本篇文章將以Intel CPU作為討論基礎
一、并發的由來
- 一臺計算機有2個cpu,其中CPU1執行程式A,CPU2執行程式B,由于程式A和程式B是兩個不同的應用程式,所以它們兩個之間并不存在并發問題,
2.現在兩個CPU要執行程式A的同一段代碼,比如對變數a執行加1操作,代碼a=a+1經過匯編器編譯之后就是如下指令片段:addl $1,-4(%esp);
Intel CPU的指令集屬于復雜指令集型別(CISC),其內部也是由微指令組成,就拿上面的加1操作指令,在CPU內部執行的時候可能會分成如下三步:
(1)從記憶體中取到1放入暫存器中
movl -4(%ebp),%eax
(2)對暫存器中的數執行加1操作
addl $1,%eax
(3)將加操作的結果寫回記憶體中
movl %eax,-4(%ebp)
3.那么問題就來了:由于執行的是同一段代碼,我們期望最后的結果是兩個CPU各加一次,也就是a=3,但實際情況存在穿插執行:CPU1和CPU2同時執行了步驟1,此時a=1;然后CPU2執行了加法操作并把資料寫入了記憶體,此時a=2,但CPU1已經取到的值是1,執行加法指令操作,接著把結果寫入記憶體,結果依然是a=2,與我們預期的a=3不符,造成資料紊亂,造成資料紊亂的根本原因就是穿插執行,多個CPU操作同一個可讀可寫的資料就會有并發問題,那在CPU層面是如何解決并發問題呢?
二、CPU解決并發問題的方案
1.對總線上鎖
為了解決并發問題,我們能想到的就是把以上三個步驟的代碼當作一個整體,要么全部不執行,要么全部執行不能被穿插,我們稱之為原子性代碼:將一串操作定義為不能拆散的基本操作,
CPU在訪存的時候需要通過控制總線、資料總線和地址總線相結合從記憶體讀寫資料,由此可以得知第一種解決辦法就是對總線上鎖,但這種方法有個很大的弊端,一旦鎖了總線,就算其他CPU執行的不是同一個程式的代碼,那么這些CPU也得等待,這將造成嚴重的性能損耗,鎖的粒度太大,為此我們考慮如何將鎖細粒度化?
2.對快取行上鎖
CPU內部有快取,為了加速訪問,CPU會把資料a相鄰連續的一段記憶體空間資料(快取行)加載到快取中,為此得到了我們第二種解決資料紊亂的辦法:對快取行上鎖,一旦資料a發生改變,會根據CPU的MESI協議來更新資料,達到資料一致性,[關于CPU的MESI協議可以參考本篇文章]
3.原子性指令
綜上所述,intel cpu提供了原子性指令,只要指令支持lock前綴,那么它就可以把這條指令變成原語指令(原語:原子性的語意),比如 add,加上lock前綴就是 lock;add ,那么加法操作就變成了原子性操作,不會再被穿插執行了,但也有一些指令比較特殊,它們本身就是原語指令,不需要lock前綴,比如:xchg,
三、上層應用的并發解決方案
只有CPU提供了原子性指令,上層應用才能夠根據這些指令來設計出指令段與指令段之間的原子性操作,這是一種自底向上的設計,沒有CPU最底層的支持,上層應用根本就無法解決并發問題,應用程式使用自身語言提供的并發操作函式庫,比如java的juc包,而這些函式庫又會封裝OS的系統呼叫或者使用glibc庫,OS的系統呼叫最侄訓使用CPU提供的原子性指令,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/509189.html
標籤:其他
