C#中lock死鎖實例教程


http://www.jb51.net/article/54309.htm

在c#中有個關鍵字lock,它的作用是鎖定某一代碼塊,讓同一時間只有一個線程訪問該代碼塊,本文就來談談lock關鍵字的原理和其中應注意的幾個問題:

lock的使用原型是:

?
1
2
3
4
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) 問題:

假定有兩個類: 

?
1
2
class A{}
class B{}

有兩個公共對象: 

?
1
2
A a= new A();
B b= new B();

首先在A中若有一函數內的代碼需要鎖定:

代碼1: 

?
1
2
3
4
5
6
7
8
lock ( this ) //this在這里就是a
{
  //....
  lock (b)
  {
//......
  }
}

然而此時B中某函數也有如下代碼需要鎖定:

代碼2: 

?
1
2
3
4
5
6
7
8
lock ( this ) //this在這里就是b
{
  //....
  lock (a)
  {
//......
  }
}

設想一下上面兩段代碼在兩個線程下同時執行會有什么后果?

結果就是,代碼1執行到lock(this)后a被鎖定,代碼2執行到lock(this)后b被鎖定,然后代碼1需求b,代碼2需求a,此時兩個需求都被相互占有出現僵持狀態,程序死鎖了。

(2)lock(typeof (MyType))問題:

假定有兩個公共變量:

?
1
int a; float b;

下面看如下代碼

代碼3:

?
1
2
3
4
5
6
7
8
lock ( typeof (a)) //typeof(a)就是System.type.Int類型
{
  //....
  lock ( typeof (b))
  {
//......
  }
}

又有如下代碼:

代碼4:

?
1
2
3
4
5
6
7
8
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)“暫留”。這意味着整個程序中任何給定字符串都只有一個實例,就是這同一個對象表示了所有運行的應用程序域的所有線程中的該文本。因此,只要在應用程序進程中的任何位置處具有相同內容的字符串上放置了鎖,就將鎖定應用程序中該字符串的所有實例。

言下之意就是假定有兩個類分別有兩個字符串:

?
1
2
3
4
5
6
7
8
9
10
11
class A
{
  string a= "abc" ;
  string b= "def" ;
}
 
class c
{
  string c= "abc" ;
  string d= "def" ;
}

事實上a和c引用的是同一個字符串"abc",b和d引用的是同一個字符串"def"

現在如果在兩個類中有如下代碼

在類A中有代碼5: 

?
1
2
3
4
5
6
7
8
lock (b) //b是"def"
{
  //....
  lock (a) //a是"abc"
  {
//......
  }
}

在類B中有代碼6: 

?
1
2
3
4
5
6
7
8
lock (c) //c是"abc"
{
  //....
  lock (d) //d是"def"
  {
//......
  }
}

那么代碼5和代碼6同時有兩個線程執行結果可想而知:在兩個線程執行到外層lock代碼時"def"和"abc"被鎖定。接着他們在內部lock處同時需求"abc"和"def",而此時兩個字符串被兩個進程彼此占有,程序又死鎖了!所以MSDN說:鎖定字符串尤其危險!最好不要使用!

MSDN最后說了:最佳做法是定義 private 對象來鎖定, 或 private shared 對象變量來保護所有實例所共有的數據。 
在個人看來,也不是絕對安全,這里就舉出一個例子:

假定有一個類: 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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,它的作用是鎖定某一代碼塊,讓同一時間只有一個線程訪問該代碼塊,本文就來談談lock關鍵字的原理和其中應注意的幾個問題:

lock的使用原型是:

?
1
2
3
4
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) 問題:

假定有兩個類: 

?
1
2
class A{}
class B{}

有兩個公共對象: 

?
1
2
A a= new A();
B b= new B();

首先在A中若有一函數內的代碼需要鎖定:

代碼1: 

?
1
2
3
4
5
6
7
8
lock ( this ) //this在這里就是a
{
  //....
  lock (b)
  {
//......
  }
}

然而此時B中某函數也有如下代碼需要鎖定:

代碼2: 

?
1
2
3
4
5
6
7
8
lock ( this ) //this在這里就是b
{
  //....
  lock (a)
  {
//......
  }
}

設想一下上面兩段代碼在兩個線程下同時執行會有什么后果?

結果就是,代碼1執行到lock(this)后a被鎖定,代碼2執行到lock(this)后b被鎖定,然后代碼1需求b,代碼2需求a,此時兩個需求都被相互占有出現僵持狀態,程序死鎖了。

(2)lock(typeof (MyType))問題:

假定有兩個公共變量:

?
1
int a; float b;

下面看如下代碼

代碼3:

?
1
2
3
4
5
6
7
8
lock ( typeof (a)) //typeof(a)就是System.type.Int類型
{
  //....
  lock ( typeof (b))
  {
//......
  }
}

又有如下代碼:

代碼4:

?
1
2
3
4
5
6
7
8
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)“暫留”。這意味着整個程序中任何給定字符串都只有一個實例,就是這同一個對象表示了所有運行的應用程序域的所有線程中的該文本。因此,只要在應用程序進程中的任何位置處具有相同內容的字符串上放置了鎖,就將鎖定應用程序中該字符串的所有實例。

言下之意就是假定有兩個類分別有兩個字符串:

?
1
2
3
4
5
6
7
8
9
10
11
class A
{
  string a= "abc" ;
  string b= "def" ;
}
 
class c
{
  string c= "abc" ;
  string d= "def" ;
}

事實上a和c引用的是同一個字符串"abc",b和d引用的是同一個字符串"def"

現在如果在兩個類中有如下代碼

在類A中有代碼5: 

?
1
2
3
4
5
6
7
8
lock (b) //b是"def"
{
  //....
  lock (a) //a是"abc"
  {
//......
  }
}

在類B中有代碼6: 

?
1
2
3
4
5
6
7
8
lock (c) //c是"abc"
{
  //....
  lock (d) //d是"def"
  {
//......
  }
}

那么代碼5和代碼6同時有兩個線程執行結果可想而知:在兩個線程執行到外層lock代碼時"def"和"abc"被鎖定。接着他們在內部lock處同時需求"abc"和"def",而此時兩個字符串被兩個進程彼此占有,程序又死鎖了!所以MSDN說:鎖定字符串尤其危險!最好不要使用!

MSDN最后說了:最佳做法是定義 private 對象來鎖定, 或 private shared 對象變量來保護所有實例所共有的數據。 
在個人看來,也不是絕對安全,這里就舉出一個例子:

假定有一個類: 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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#程序設計有所幫助。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM