多執行緒和多核下“鎖”的應用
假設這樣一種情況:有多個執行緒(或多核)需要在共享資料A滿足某一條件時,對A進行操作.
以下舉例兩種實現
Fun_1()
{ lock()--------------1.1
Result=Check(A)-----1.2
Unlock()------------1.3
-------------RISK!!!!
Lock()--------------1.4
Operate(A,result)---1.5
Unlock()------------1.6
}
Fun_2()
{ lock()--------------2.1
Result=Check(A)-----2.2
Operate(A,result)---2.3
Unlock()------------2.4
}
以上函式,只有fun_2 是安全的,FUN_1存在風險:
單核下:
當前執行緒完1.3 後如果發生了執行緒排程,那麼資料a可能被其它執行緒修改。
重新回到1.4時,RESULT 已經不對了!!!
多核下:
當前執行緒完1.3 後如果發生了執行緒排程,那麼資料a可能被其它執行緒修改。
重新回到1.4時,RESULT 已經不對了!!!
當前執行緒完1.3 後如果第二個核正好在執行程式碼1.5,那麼資料a會被修改。
重新回到1.4時,RESULT 已經不對了!!!
***********************************************************************************************
鎖的選擇分為“訊號量”“和自旋轉鎖”:
訊號量會使程式休眠
自旋鎖會關搶佔,並進行忙等待
一般如果訪問時間較短,或者使用多核處理器可以選擇自旋鎖
如果訪問的時間較長(大於系統進行兩次排程需要的時間)且是單核處理器,則可以選擇訊號量。
************************************************************************************************
linux下自旋鎖函式:
spin_lock 使用情況:如果被保護的共享資源“只在程序上下文訪問”
功能: 最基本的鎖,沒有對中斷處理
spin_lock_bh 適用情況:如果被保護的共享資源“只在程序上下文和tasklet或timer上下文訪問”
功能: spin_lock為基礎,釋放時開啟上鎖時禁止的軟中斷
spin_lock_irq 適用情況:如果被保護的共享資源“只在程序上下文和tasklet或timer上下文訪問”
功能: spin_lock為基礎,釋放時開啟中斷
spin_lock_irqsave 適用情況:效率最差的一種,但當你不知道用哪個好時,可以用它
功能: spin_lock為基礎,釋放時恢復上鎖時的中斷狀態
*******************************************************************************************************************
以下說明摘自網上:
如果被保護的共享資源“只在程序上下文訪問和軟中斷上下文訪問”,那麼當在程序上下文訪問共享資源時,可能被軟中斷打斷,從而可能進入軟中斷上下文來對被保護的共享資源訪問,因此對於這種情況,對共享資源的訪問必須使用spin_lock_bh和spin_unlock_bh來保護。
當然使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和spin_unlock_irqrestore也可以,它們失效了本地硬中斷,失效硬中斷隱式地也失效了軟中斷。但是使用spin_lock_bh和spin_unlock_bh是最恰當的,它比其他兩個快。 如果被保護的共享資源“只在程序上下文和tasklet或timer上下文訪問”,那麼應該使用與上面情況相同的獲得和釋放鎖的巨集,因為tasklet和timer是用軟中斷實現的。 如果被保護的共享資源“只在一個tasklet或timer上下文訪問”,那麼不需要任何自旋鎖保護,因為同一個tasklet或timer只能在一個CPU上執行,即使是在SMP環境下也是如此。實際上tasklet在呼叫tasklet_schedule標記其需要被排程時已經把該tasklet繫結到當前CPU,因此同一個tasklet決不可能同時在其他CPU上執行。 timer也是在其被使用add_timer新增到timer佇列中時已經被幫定到當前CPU,所以同一個timer絕不可能執行在其他CPU上。當然同一個tasklet有兩個例項同時執行在同一個CPU就更不可能了。 如果被保護的共享資源“只在兩個或多個tasklet或timer上下文”訪問,那麼對共享資源的訪問僅需要用spin_lock和spin_unlock來保護,不必使用_bh版本,因為當tasklet或timer執行時,不可能有其他tasklet或timer在當前CPU上執行。 如果被保護的共享資源“只在一個軟中斷(tasklet和timer除外)上下文訪問”,那麼這個共享資源需要用spin_lock和spin_unlock來保護,因為同樣的軟中斷可以同時在不同的CPU上執行。 如果被保護的共享資源在兩個或多個軟中斷上下文訪問,那麼這個共享資源當然更需要用spin_lock和spin_unlock來保護,不同的軟中斷能夠同時在不同的CPU上執行。 如果被保護的共享資源在軟中斷(包括tasklet和timer)或程序上下文和硬中斷上下文訪問,那麼在軟中斷或程序上下文訪問期間,可能被硬中斷打斷,從而進入硬中斷上下文對共享資源進行訪問,因此,在程序或軟中斷上下文需要使用spin_lock_irq和spin_unlock_irq來保護對共享資源的訪問。 而在中斷處理控制代碼中使用什麼版本,需依情況而定,如果只有一箇中斷處理控制代碼訪問該共享資源,那麼在中斷處理控制代碼中僅需要spin_lock和spin_unlock來保護對共享資源的訪問就可以了。 因為在執行中斷處理控制代碼期間,不可能被同一CPU上的軟中斷或程序打斷。但是如果有不同的中斷處理控制代碼訪問該共享資源,那麼需要在中斷處理控制代碼中使用spin_lock_irq和spin_unlock_irq來保護對共享資源的訪問。 在使用spin_lock_irq和spin_unlock_irq的情況下,完全可以用spin_lock_irqsave和spin_unlock_irqrestore取代,那具體應該使用哪一個也需要依情況而定,如果可以確信在對共享資源訪問前中斷是使能的,那麼使用spin_lock_irq更好一些。 因為它比spin_lock_irqsave要快一些,但是如果你不能確定是否中斷使能,那麼使用spin_lock_irqsave和spin_unlock_irqrestore更好,因為它將恢復訪問共享資源前的中斷標誌而不是直接使能中斷。 當然,有些情況下需要在訪問共享資源時必須中斷失效,而訪問完後必須中斷使能,這樣的情形使用spin_lock_irq和spin_unlock_irq最好。