程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#線程從陌生到熟悉(4)

C#線程從陌生到熟悉(4)

編輯:C#入門知識

 

互斥與同步

今天我們來看互斥與同步,互斥與同步一直線程操作方面的至關重要的一部份.首先來看下他們之間的概念.

互斥和同步都是在並發環境下編程特有的問題.並發是指宏觀上多個線程同時進行而某一時刻只有一個線程運行,即宏觀並行微觀串行.大家應該都知道時間片的概念,上面的話應該很容易理解的.並發環境下,不能有兩個以上(包括兩個)某類特殊的資源,就叫互斥.這類特殊資源也被稱為臨界資源,例如字符串緩沖區,文件,實例對象等.同步是指多個線程互相通信,互相等待從而使進程按照一定順序往前推進.其實特殊的資源應該就是經常用到的變量與對象只不過在某一時刻只能由一個線程操作他.

1.互斥

.NET公共運行庫提供了幾種方法實現對臨界資源的互斥訪問.

首先看Monitor這個類

[ComVisible(true)]

public static class Monitor

{

[SecuritySafeCritical]

public static void Enter(object obj);

[SecuritySafeCritical]

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]

public static void Exit(object obj);

[SecuritySafeCritical]

public static void Pulse(object obj);

public static bool TryEnter(object obj);

[SecuritySafeCritical]

public static bool Wait(object obj);

.....

}

這幾個方法簡單介紹,Enter獲取對象鎖(參數obj),Exit釋放對象鎖,Pulse通知等待隊列中的線程鎖定對象狀態的更改,TryEnter嘗試獲取對象鎖,Wait釋放對象上的鎖並阻止當前線程,直到它重新獲取該鎖。

下面來看個例子:

 1 using System;

 2 using System.Collections.Generic;

 3 using System.Linq;

 4 using System.Text;

 5 using System.Threading;

 6

 7 namespace ConsoleApplication8

 8 {

 9     class Program

10     {

11         int x = 100;

12         static void Main(string[] args)

13         {

14             Program p = new Program();

15             Thread t1 = new Thread(p.MyWork1);

16             Thread t2 = new Thread(p.MyWork2);

17             t1.Start();

18             t2.Start();

19         }

20         void MyWork1()

21         {

22             try

23             {

24                 Monitor.Enter(this);

25                 while (true)

26                 {

27                   

28                     if (x < 1) { break; }

29                     x--;

30                     Console.WriteLine("調用MyWork1()");

31                 }

32             }

33             finally

34             {

35                 Monitor.Exit(this);

36             }

37         }

38         void MyWork2()

39         {

40           

41             try

42             {

43                 Monitor.Enter(this);

44                 while (true)

45                 {

46

47                     if (x < 1) { break; }

48                     x = x - 2;

49                     Console.WriteLine("調用MyWork2()");

50

51                 }

52             }

53             finally

54             {

55                 Monitor.Exit(this);

56             }

57         }

58     }

59 }

輸出結果為

 

\

 

結果全為MyWork1的調用,如果將Monitor.Enter(this)和Monitor.Exit(this)注釋起來的話,在運行的話,就會出現MyWork1和Mywork2都在運行了.還有可以將MyWork1中的x<1改成10那運行結果為

 

\

 

可見MyWork1先運行了,當他釋放完鎖之後MyWork2獲取到對象所之後才運行.從這個例子我們可以看出Monitor.Enter(this)和Monitor.Exit(this)的用法.按照我的理解:Monitor.Enter(this)會一直等待,直到獲到對象鎖.對於這個猜測,我們可以在MyWork2的Monitor.Enter(this)加一句輸出語句.可以看這個輸出結果會在控制台上.

將上面的例子稍加更改,來重點說下TryEnter,Wait,Pulse這三函數的用法及相關解釋.

先看代碼

 

 1 using System;

 2 using System.Collections.Generic;

 3 using System.Linq;

 4 using System.Text;

 5 using System.Threading;

 6

 7 namespace ConsoleApplication8

 8 {

 9     class Program

10     {

11         int x = 100;

12         static void Main(string[] args)

13         {

14             Program p = new Program();

15             Thread t1 = new Thread(p.MyWork1);

16             Thread t2 = new Thread(p.MyWork2);

17             t1.Start();

18            // Thread.Sleep(2000);

19             t2.Start();

20         }

21         void MyWork1()

22         {

23             try

24             {

25                 Monitor.Enter(this);

26                // Monitor.Pulse(this);

27                 Monitor.Wait(this);

28                 while (true)

29                 {

30                   

31                     if (x < 1) { break; }

32                     x--;

33                     Console.WriteLine("調用MyWork1()");

34                 }

35             }

36             finally

37             {

38                 try

39                 {

40                     Monitor.Exit(this);

41                 }

42                 catch (Exception e)

43                 {

44                     Console.WriteLine("MyWork1異常退出,信息為");

45                     Console.WriteLine(e.Message);

46                 }

47             }

48         }

49         void MyWork2()

50         {

51

52             try

53             {

54                 //Monitor.Enter(this)

55                 if (Monitor.TryEnter(this))

56                 {

57                     Monitor.Pulse(this);

58                     while (true)

59                     {

60

61                         if (x < 10) { break; }

62                         x = x - 2;

63                         Console.WriteLine("調用MyWork2()");

64

65                     }

66                 }

67             }

68             finally

69             {

70                 try

71                 {

72                    

73                     Monitor.Exit(this);

74                    

75                 }

76                 catch (Exception e)

77                 {

78                     Console.WriteLine("MyWork2異常退出,信息為");

79                     Console.WriteLine(e.Message);

80                 }

81             }

82         }

83     }

84 }

 

 

運行結果如下圖

 

\

 

 

程序的執行流程為,首先MyWork1獲得對象鎖,然後調用Wait方法釋放鎖,阻止當前線程,直到再次獲得對象鎖為止.MyWork2此時運行,調用TryEnter這個方法,注意了,這個方法和Enter有很大的區別的,他會立即返回結果.不像Enter會一直在那等.因為之前對象鎖已經釋放,所以返回真.如果將Wait方法注釋的話,MyWork2裡的TryEnter方法將立即返回假,程序異常結束.接著調用Pulse方法,通知其他線程鎖定對象狀態的更改,當X<10對象鎖釋放,而一直處於等待MyWork1將再次獲得對象鎖.執行相應的動作.這裡我要說下Pulse這個方法,如果MyWork2將這個方法注釋,那麼MyWork1這個線程將一直處於等待狀態.一直獲不到對象鎖的.

通過這兩個例子,應該對Monitor這個類比較了解了.

還有重要的東西,如果是類的靜態方法或屬性,此時就要鎖定該類型了,這個地方要注意的,在這我也給各例子:

結果這裡就不給了;

 1 using System;

 2 using System.Collections.Generic;

 3 using System.Linq;

 4 using System.Text;

 5 using System.Threading;

 6

 7 namespace ConsoleApplication9

 8 {

 9     class Program

10     {

11         static void Main(string[] args)

12         {

13             Thread t1 = new Thread(MyWork1);

14             Thread t2 = new Thread(MyWork2);

15             t1.Start();

16             t2.Start();

17         }

18         static void MyWork1()

19         {

20             Monitor.Enter(typeof(Program));

21             for (int i = 1; i < 20; i++)

22             {

23                 Console.WriteLine("MyWork1()");

24                 Thread.Sleep(10);

25             }

26             Monitor.Exit(typeof(Program));

27         }

28         static void MyWork2()

29         {

30             Monitor.Enter(typeof(Program));

31             for (int i = 1; i < 20; i++)

32             {

33                 Console.WriteLine("MyWork2()");

34                 Thread.Sleep(10);

35             }

36             Monitor.Exit(typeof(Program));

37         }

38     }

39 }

 

 

下面看.Net比較簡單操作同步的方法

 如果臨界區跨越整個方法,則可以通過將using System.Runtime.CompilerServices的MethodImplAttribute放置在方法並在MethodImplAttribute德構造函數中制定MethodImplOptions.Synchronized值來實現上述鎖定功能.對於上面的例子,可作如下更改

   [MethodImpl(MethodImplOptions.Synchronized)]

   static void MyWork1()

   {

            //Monitor.Enter(typeof(Program));

            for (int i = 1; i < 20; i++)

            {

                Console.WriteLine("MyWork1()");

                Thread.Sleep(10);

            }

           // Monitor.Exit(typeof(Program));

   }

MyWork2也一樣,運行結果一樣.

lock關鍵字,lock(expression)必須是引用類型.

可將上面的例子作如下更改 

 static   void MyWork1()

{

       lock (typeof(Program))

      {

                for (int i = 1; i < 20; i++)

                {

                    Console.WriteLine("MyWork1()");

                    Thread.Sleep(10);

                }

      }

 }

這裡注意下MyWork1是靜態方法,所以鎖定類型,大多時候我們都是用lock(this);

ReaderWriterLock這個類有著重多的優點,比如開銷低,支持超時等等.看下這個類的定義以及常用的方法

[ComVisible(true)]

public sealed class ReaderWriterLock : CriticalFinalizerObject

{

 public void AcquireReaderLock(int millisecondsTimeout);

   public void AcquireWriterLock(int millisecondsTimeout);

   public LockCookie ReleaseLock();

   public void ReleaseReaderLock();

   public void ReleaseWriterLock();

......

}

請看下面的例子

 1 using System;

 2 using System.Collections.Generic;

 3 using System.Linq;

 4 using System.Text;

 5 using System.Threading;

 6

 7 namespace ConsoleApplication10

 8 {

 9     class Program

10     {

11         int value = 100;

12         ReaderWriterLock rwl = new ReaderWriterLock();

13         static void Main(string[] args)

14         {

15             Program p = new Program();

16             Thread tr1 = new Thread(p.Read);

17             Thread tw1 = new Thread(p.Write);

18             tr1.Start();

19             tw1.Start();

20

21

22         }

23         void Read()

24         {

25             rwl.AcquireReaderLock(Timeout.Infinite);

26             Console.WriteLine("Read開始讀");

27             Thread.Sleep(10);

28             Console.WriteLine("Read讀出的Value值為{0}", value);

29             Console.WriteLine("Read結束讀");

30             rwl.ReleaseReaderLock();

31         }

32         void Write()

33         {

34             rwl.AcquireWriterLock(Timeout.Infinite);

35             Console.WriteLine("Write開始寫");

36             Thread.Sleep(10);

37             value++;

38             Console.WriteLine("Write寫完後的Value值為{0}", value);

39             Console.WriteLine("Write結束寫");

40             rwl.ReleaseReaderLock();

41         }

42     }

43 }

www.2cto.com

結果為

 

\

 

大家可以看到讀的時候鎖定對象,無法寫入,換兩個線程的開始位置,就可以得到寫的時候鎖定對象,讀的操作無法讀取.

這裡有兩個方法有點意思的public LockCookie UpgradeToWriterLock(int millisecondsTimeout)和public LockCookie UpgradeToWriterLock(TimeSpan timeout)這個方法是讀寫線程鎖的轉換.大家可以自己嘗試下.

好了,今天到這.關於互斥還有些內容,下次再討論!

摘自 ENUO

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved