前言
隨著時間的推移,共享內存已經在修真界已經淪為禁術。因為使用這種方式溝通的兩人往往會陷入到走火入魔的狀態,思維扭曲。進程君父子見到這種情況,連忙開始專研起來,終于它們發現了共享內存存在的問題:
進程間沖突
我們現在假設這樣一種情況,有三個進程,它們使用共享內存完成通信。進程1向共享內存中寫入一些數據,想讓進程2讀取這些數據。很不巧,由于缺乏管理,現在進程3也同時在向共享內存中寫入數據。進程1與進程3的數據發生了相互沖突與相互覆蓋,對于進程2來說,讀取到了一些無意義的數據。如下圖所示,這種相互沖突的問題是也是共享內存最大的局限性。如何解決不是我們這一節的重點,我們下一節再討論,請大家繼續向后看。
上面這種情況屬于比較容易理解的范疇,下面這個就比較抽象了。
現在假設一種情況,我們在共享內存中定義了一個變量x,初始值為0,現在有兩個進程,同時對這個變量進行加一操作,最后這個變量的值應該是多少?
既然我都這么問了,當然答案不可能是2,實際上,我們無法判斷這個變量最終的值,它可能是1,也可能是2,可能每次運行結果都不同。這種反直覺的現象是什么原因造成的呢?接下來我帶大家分析一下。
在c或者c++中,我們對一個變量x進行加一操作,無非一下兩種手段,這里假設x已經被定義并且初始化為0。
// 方案1
x = x + 1;// 方案2
x++;
大家在上面看到的是兩條語句,因此可能想當然的認為這兩條語句每條都是一次直接執行完畢。但是實際上,對于這兩條語句中的任意一條語句,他的執行大概分為三步。我們先編寫一段C語言代碼。在這里為了模擬共享內存,我們在全局區定義x并初始化為0,模擬x在共享內存中的情況。
#include <stdio.h>int x = 0;int main() {x = x + 1;return 0;
}
我們將這段代碼進行匯編,觀察它的匯編代碼,如下所示,根據#注釋的內容我們可以看到,main.c文件的第六行的x=x+1;對應著三條匯編語句,它們分別是:(1)把變量x從內存中轉移到CPU寄存器eax中,(2)在寄存器eax中對變量x加一,(3)把處理后的變量x從寄存器中放回到內存中。
main:
.LFB0:# 進入main函數后需要壓棧
# main.c:6: x = x + 1;movl x(%rip), %eax # x, x.0_1addl $1, %eax #, _2
# main.c:6: x = x + 1;movl %eax, x(%rip) # _2, x
# main.c:7: return 0;movl $0, %eax #, _5
# main.c:8: }# 即將離開main函數,需要出棧ret .cfi_endproc
每一條匯編指令都是原子的,也就是不會被進程切換打斷的。但是對于單核CPU在每兩條匯編指令之間,都有可能會發生進程的切換。對于多核CPU,也可能會出現同時處理的情況,這會造成什么影響呢?我們用下面的圖來表示:
小結
今天我們詳細分析了共享內存可能存在的問題。雖然它的傳輸速度快,節約資源,但是如果不加以約束,一定會出現問題。
那么如何在共享內存中加入約束,讓兩個進程間互不干擾呢?這就是我們下一節要研究的問題:信號量。
結束語
進程君父子找到了共享內存存在的局限性,它們打算提供一個補救方案,方案定制中。