併發
是指在某一時間段內能夠處理多個任務的能力,而 並行 是指同一時間能夠處理多個任務的能力。併發和並行看起來很像,但實際上是有區別的,如下圖:
在這裡插入描述
上圖的意思是,有兩條在排隊買咖啡的佇列,併發只有一架咖啡機在處理,而並行就有兩架的咖啡機在處理。咖啡機的數量越多,並行能力就越強。
可以把上面的兩條佇列看成兩個程序,併發就是指只有單個cpu在處理,而並行就有兩個cpu在處理。為了讓兩個程序在單核cpu中也能得到執行,一般的做法就是讓每個程序交替執行一段時間,比如讓每個程序固定執行 100毫秒,執行時間使用完後切換到其他程序執行。而並行就沒有這種問題,因為有兩個cpu,所以兩個程序可以同時執行。如下圖:
在這裡插入描述
原子操作
上面介紹過,併發有可能會打斷當前執行的程序,然後替切換成其他程序執行。如果有兩個程序同時對乙個共享變數 count 進行加一操作,由於c語言的 count++ 操作會被翻譯成如下指令:
mov eax, [count]
inc eax
mov [count], eax12
3那麼在併發的情況下,有可能出現如下問題:
在這裡插入描述
假設count變數初始值為0:
程序1執行完 mov eax, [count] 後,暫存器eax內儲存了count的值0。
程序2被排程執行。程序2執行 count++ 的所有指令,將累加後的count值1寫回到記憶體。
程序1再次被排程執行,計算count的累加值仍為1,寫回到記憶體。
雖然程序1和程序2執行了兩次 count++ 操作,但是count最後的值為1,而不是2。
要解決這個問題就需要使用 原子操作,原子操作是指不能被打斷的操作,在單核cpu中,一條指令就是原子操作。比如上面的問題可以把 count++ 語句翻譯成指令 inc [count] 即可。linux也提供了這樣的原子操作,如對整數加一操作的 atomic_inc():
staticinlinevoid atomic_inc(atomic_t *v)12
3456
7在多核cpu中,一條指令也不一定是原子操作,比如 inc [count] 指令在多核cpu中需要進行如下過程:
從記憶體將count的資料讀取到cpu。
累加讀取的值。
將修改的值寫回count記憶體。
intel x86 cpu 提供了 lock 字首來鎖住匯流排,可以讓指令保證不被其他cpu中斷,如下:
lock
inc [count]12
鎖原子操作 能夠保證操作不被其他程序干擾,但有時候乙個複雜的操作需要由多條指令來實現,那麼就不能使用原子操作了,這時候可以使用 鎖 來實現。
電腦科學中的 鎖 與日常生活的 鎖 有點類似,舉個例子:比如要上公廁,首先找到乙個沒有人的廁所,然後把廁所門鎖上。其他人要使用的話,必須等待當前這人使用完畢,並且把門鎖開啟才能使用。在計算機中,要對某個公共資源進行操作時,必須對公共資源進行上鎖,然後才能使用。如果不上鎖,那麼就可能導致資料混亂的情況。
在linux核心中,比較常用的鎖有:自旋鎖、訊號量、讀寫鎖 等,下面介紹一下自旋鎖和訊號量的實現。
使用 自旋鎖 時,必須先對自旋鎖進行初始化(設定為1),上鎖過程如下:
對自旋鎖 lock 進行減一操作,判斷結果是否等於0,如果是表示上鎖成功並返回。
如果不等於0,表示其他程序已經上鎖,此時必須不斷比較自旋鎖 lock 的值是否等於1(表示已經解鎖)。
如果自旋鎖 lock 等於1,跳轉到第一步繼續進行上鎖操作。
由於linux的自旋鎖使用彙編實現,所以比較苦澀難懂,這裡使用c語言來模擬一下:
void spin_lock(amtoic_t *lock)
while (true)
}
}12
3456
78910
1112
1314
上面**將 result = --(*lock); 當成原子操作,解鎖過程只需要把 lock 設定為1即可。由於自旋鎖會不斷嘗試上鎖操作,並不會對程序進行排程,所以在單核cpu中可能會導致 100% 的cpu佔用率。另外,自旋鎖只適合粒度比較小的操作,如果操作粒度比較大,就需要使用訊號量這種可排程程序的鎖。
訊號量與 自旋鎖 不一樣,當當前程序對 訊號量 進行上鎖時,如果其他程序已經對其進行上鎖,那麼當前程序會進入睡眠狀態,等待其他程序對訊號量進行解鎖。過程如下圖:
在這裡插入描述
在linux核心中,訊號量使用 struct semaphore 表示,定義如下:
struct semaphore ;12
345各個欄位的作用如下:
lock:自旋鎖,用於對多核cpu平台進行同步。
count:訊號量的計數器,上鎖時對其進行減一操作(count–),如果得到的結果為大於等於0,表示成功上鎖,如果小於0表示已經被其他程序上鎖。
wait_list:正在等待訊號量解鎖的程序佇列。
訊號量 上鎖通過 down() 函式實現,**如下:
void down(struct semaphore *sem)12
3456
78910
11上面**可以看出,down() 函式首先對訊號量進行自旋鎖操作(為了避免多核cpu競爭),然後比較計數器是否大於0,如果是對計數器進行減一操作,並且返回,否則呼叫 __down() 函式進行下一步操作。__down() 函式實現如下:
static noinline void __sched __down(struct semaphore *sem)
static inline int __down_common(struct semaphore *sem,
long state, long timeout)
...}12
3456
78910
1112
1314
1516
1718
1920
2122
2324
2526
27__down() 函式最終呼叫 __down_common() 函式,而 __down_common() 函式的操作過程如下:
把當前程序新增到訊號量的等待佇列中。
切換到其他程序執行,直到被其他程序喚醒。
如果當前程序獲得訊號量鎖(由解鎖程序傳遞),那麼函式返回。
接下來看看解鎖過程,解鎖過程主要通過 up() 函式實現,**如下:
void up(struct semaphore *sem)12
3456
78910
1112
1314
1516
1718
1920
2122
解鎖過程如下:
判斷當前訊號量是否有等待的程序,如果沒有等待的程序, 直接對計數器加一操作
如果有等待的程序,那麼獲取到等待佇列的第乙個程序。
把程序從等待佇列中刪除。
告訴程序已經獲得訊號量鎖。
喚醒程序。
深入解析Linux併發同步
是指在某一時間段內能夠處理多個任務的能力,而 並行 是指同一時間能夠處理多個任務的能力。併發和並行看起來很像,但實際上是有區別的,如下圖 上圖的意思是,有兩條在排隊買咖啡的佇列,併發只有一架咖啡機在處理,而並行就有兩架的咖啡機在處理。咖啡機的數量越多,並行能力就越強。epoll的具體實現與epoll...
LINUX c 併發同步
5.1 核心同步與死鎖問題 併發 兩個程序可以真正的在臨界區中同時執行。原因 1 中斷 2 軟中斷和tasklet 3 核心搶占 4 睡眠及與使用者空間的同步 5 對稱多處理 資料加鎖 1 如果有其他執行執行緒可以訪問這些資料 2 如果任何其他什麼東西都能看見它 3 幾乎訪問所有的核心全域性變數和共...
深入同步語法
1.深入synchronized關鍵字 class servicecatch exception e system.outprintln fun1 public void fun2 class mythread1 implements runnable public void run class m...