標籤:
這篇文章主要介紹了C#中lock死結的用法,對於共用資源的訪問及C#程式設計的安全性而言,有著非常重要的意義!需要的朋友可以參考下
連結:http://www.jb51.net/article/54309.htm
在c#中有個關鍵字lock,它的作用是鎖定某一代碼塊,讓同一時間只有一個線程訪問該代碼塊,本文就來談談lock關鍵字的原理和其中應注意的幾個問題:
lock的使用原型是:
?
1234 |
lock (X) { //需要鎖定的代碼.... } |
首先要明白為什麼上面這段話能夠鎖定代碼,其中的奧妙就是X這個對象,事實上X是任意一種參考型別,它在這兒起的作用就是任何線程執行到lock(X)時候,X需要獨享才能運行下面的代碼,若假定現在有3個線程A,B,C都執行到了lock(X)而ABC因為此時都佔有X,這時ABC就要停下來排個隊,一個一個使用X,從而起到在下面的代碼塊內只有一個線程在運行(因為此時只有一個線程獨享X,其餘兩個在排隊),所以這個X必須是所有要執行臨界地區代碼進程必須共有的一個資源,從而起到抑制線程的作用。
下面再來談談lock使用中會遇到和注意的問題,lock最需要注意的一個問題就是線程死結!
在MSDN上列出了3個典型問題:
通常,應避免鎖定 public 類型,否則執行個體將超出代碼的控制範圍。常見的結構 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 違反此準則:
如果執行個體可以被公用訪問,將出現 lock (this) 問題。
如果 MyType 可以被公用訪問,將出現 lock (typeof (MyType)) 問題。
由於進程中使用同一字串的任何其他代碼將共用同一個鎖,所以出現 lock(“myLock”) 問題。
最佳做法是定義 private 對象來鎖定, 或 private shared 物件變數來保護所有執行個體所共有的資料。
(1)lock (this) 問題:
假定有兩個類:
?
有兩個公用對象:
?
12 |
A a= new A(); B b= new B(); |
首先在A中若有一函數內的代碼需要鎖定:
代碼1:
?
12345678 |
lock ( this ) //this在這裡就是a { //.... lock (b) { //...... } } |
然而此時B中某函數也有如下代碼需要鎖定:
代碼2:
?
12345678 |
lock ( this ) //this在這裡就是b { //.... lock (a) { //...... } } |
設想一下上面兩段代碼在兩個線程下同時執行會有什麼後果?
結果就是,代碼1執行到lock(this)後a被鎖定,代碼2執行到lock(this)後b被鎖定,然後代碼1需求b,代碼2需求a,此時兩個需求都被相互佔有出現僵持狀態,程式死結了。
(2)lock(typeof (MyType))問題:
假定有兩個公開變數:
?
下面看如下代碼
代碼3:
?
12345678 |
lock ( typeof (a)) //typeof(a)就是System.type.Int類型 { //.... lock ( typeof (b)) { //...... } } |
又有如下代碼:
代碼4:
?
12345678 |
lock ( typeof (b)) //typeof(b)就是System.type.Float類型 { //.... lock ( typeof (a)) { //...... } } |
若有兩個進程分別同時進入上面兩個代碼外層的lock,就分別鎖定了System.type.Int和System.type.Float,而馬上它們又需求System.type.Float和System.type.Int,彼此相互佔有,彼此僵持,程式進入死結狀態!
(3)字串問題 :
在闡述這個問題之前,有一個知識大家必須知道:C#中字串被公用語言運行庫 (CLR)“暫留”。這意味著整個程式中任何給定字串都只有一個執行個體,就是這同一個對象表示了所有啟動並執行應用程式定義域的所有線程中的該文本。因此,只要在應用程式進程中的任何位置處具有相同內容的字串上放置了鎖,就將鎖定應用程式中該字串的所有執行個體。
言下之意就是假定有兩個類分別有兩個字串:
?
1234567891011 |
class A { string a= "abc" ; string b= "def" ; } class c { string c= "abc" ; string d= "def" ; } |
事實上a和c引用的是同一個字串"abc",b和d引用的是同一個字串"def"
現在如果在兩個類中有如下代碼
在類A中有代碼5:
?
12345678 |
lock (b) //b是"def" { //.... lock (a) //a是"abc" { //...... } } |
在類B中有代碼6:
?
12345678 |
lock (c) //c是"abc" { //.... lock (d) //d是"def" { //...... } } |
那麼代碼5和代碼6同時有兩個線程執行結果可想而知:在兩個線程執行到外層lock代碼時"def"和"abc"被鎖定。接著他們在內部lock處同時需求"abc"和"def",而此時兩個字串被兩個進程彼此佔有,程式又死結了!所以MSDN說:鎖定字串尤其危險!最好不要使用!
MSDN最後說了:最佳做法是定義 private 對象來鎖定, 或 private shared 物件變數來保護所有執行個體所共有的資料。
在個人看來,也不是絕對安全,這裡就舉出一個例子:
假定有一個類:
?
123456789101112131415161718192021222324252627 |
class A { private Object a= new Object(); private Object b= new Object(); public void x() { lock (a) { //..... lock (b) { //.... } } } public void y() { lock (b) { //..... lock (a) { //.... } } } } |
現在假定有兩個線程同時執行函數x()和y();結果private對象a和b分別在外層lock鎖定,接著兩個線程在內部又立馬需求b和a,a,b彼此佔有又彼此需求,程式死結。
所以具體要看情況而定,但是定義 private 對象來鎖定至少可以降低風險。
希望本文所述C#中lock的應用對大家的C#程式設計有所協助。
C#中lock死結執行個體教程