C#線程同步的總結

歸納一下:C#線程同步的幾種方法

  我們在編程的時候,有時會使用多線程來解決問題,比如你的程序需要在後臺處理一大堆數據,但還要使用戶界面處於可操作狀態;或者你的程序需要訪問一些外部資源如數據庫或網絡文件等。這些情況你都可以創建一個子線程去處理,然而,多線程不可避免地會帶來一個問題,就是線程同步的問題。如果這個問題處理不好,我們就會得到一些非預期的結果。

  在網上也看過一些關於線程同步的文章,其實線程同步有好幾種方法,下面我就簡單的做一下歸納。

  一、volatile關鍵字

  volatile是最簡單的一種同步方法,當然簡單是要付出代價的。它只能在變量一級做同步,volatile的含義就是告訴處理器, 不要將我放入工作內存, 請直接在主存操作我。(【轉自www.bitsCN.com 】)因此,當多線程同時訪問該變量時,都將直接操作主存,從本質上做到了變量共享。

  能夠被標識爲volatile的必須是以下幾種類型:(摘自MSDN)

Any reference type. Any pointer type (in an unsafe context). The types sbyte, byte, short, ushort, int, uint, char, float, bool. An enum type with an enum base type of byte, sbyte, short, ushort, int, or uint.

  如:

Code
public class A
{
private volatile int _i;
public int I
{
get { return _i; }
set { _i = value; }
}
}

  但volatile並不能實現真正的同步,因爲它的操作級別只停留在變量級別,而不是原子級別。如果是在單處理器系統中,是沒有任何問題的,變量在主存中沒有機會被其他人修改,因爲只有一個處理器,這就叫作processor Self-Consistency。但在多處理器系統中,可能就會有問題。 每個處理器都有自己的data cach,而且被更新的數據也不一定會立即寫回到主存。所以可能會造成不同步,但這種情況很難發生,因爲cach的讀寫速度相當快,flush的頻率也相當高,只有在壓力測試的時候纔有可能發生,而且機率非常非常小。

  二、lock關鍵字

  lock是一種比較好用的簡單的線程同步方式,它是通過爲給定對象獲取互斥鎖來實現同步的。它可以保證當一個線程在關鍵代碼段的時候,另一個線程不會進來,它只能等待,等到那個線程對象被釋放,也就是說線程出了臨界區。用法:

Code
public void Function()
{
object lockThis = new object ();
lock (lockThis)
{
// Access thread-sensitive resources.
}
}

 

 

  lock的參數必須是基於引用類型的對象,不要是基本類型像bool,int什麼的,這樣根本不能同步,原因是lock的參數要求是對象,如果傳入int,勢必要發生裝箱操作,這樣每次lock的都將是一個新的不同的對象。最好避免使用public類型或不受程序控制的對象實例,因爲這樣很可能導致死鎖。特別是不要使用字符串作爲lock的參數,因爲字符串被CLR“暫留”,就是說整個應用程序中給定的字符串都只有一個實例,因此更容易造成死鎖現象。建議使用不被“暫留”的私有或受保護成員作爲參數。其實某些類已經提供了專門用於被鎖的成員,比如Array類型提供SyncRoot,許多其它集合類型也都提供了SyncRoot。

  所以,使用lock應該注意以下幾點: 

  1、如果一個類的實例是public的,最好不要lock(this)。因爲使用你的類的人也許不知道你用了lock,如果他new了一個實例,並且對這個實例上鎖,就很容易造成死鎖。

  2、如果MyType是public的,不要lock(typeof(MyType))

  3、永遠也不要lock一個字符串

  三、System.Threading.Interlocked

  對於整數數據類型的簡單操作,可以用 Interlocked 類的成員來實現線程同步,存在於System.Threading命名空間。Interlocked類有以下方法:Increment , Decrement , ExchangeCompareExchange 。使用IncrementDecrement 可以保證對一個整數的加減爲一個原子操作。Exchange 方法自動交換指定變量的值。CompareExchange 方法組合了兩個操作:比較兩個值以及根據比較的結果將第三個值存儲在其中一個變量中。比較和交換操作也是按原子操作執行的。如:

Code
int i = 0 ;
System.Threading.Interlocked.Increment(
ref i);
Console.WriteLine(i);
System.Threading.Interlocked.Decrement(
ref i);
Console.WriteLine(i);
System.Threading.Interlocked.Exchange(
ref i, 100 );
Console.WriteLine(i);
System.Threading.Interlocked.CompareExchange(
ref i, 10 , 100 );

Output:

  四、Monitor

  Monitor類提供了與lock類似的功能,不過與lock不同的是,它能更好的控制同步塊,當調用了Monitor的Enter(Object o)方法時,會獲取o的獨佔權,直到調用Exit(Object o)方法時,纔會釋放對o的獨佔權,可以多次調用Enter(Object o)方法,只需要調用同樣次數的Exit(Object o)方法即可,Monitor類同時提供了TryEnter(Object o,[int])的一個重載方法,該方法嘗試獲取o對象的獨佔權,當獲取獨佔權失敗時,將返回false。

  但使用 lock 通常比直接使用 Monitor 更可取,一方面是因爲 lock 更簡潔,另一方面是因爲 lock 確保了即使受保護的代碼引發異常,也可以釋放基礎監視器。這是通過 finally 中調用Exit來實現的。事實上,lock 就是用 Monitor 類來實現的。下面兩段代碼是等效的:

Code
lock
(x)
{
DoSomething();
}
等效於

object obj = ( object
)x;
System.Threading.Monitor.Enter(obj);
try

{
DoSomething();
}
finally
{
System.Threading.Monitor.Exit(obj);
}

 

關於用法,請參考下面的代碼:

Code
private static object m_monitorObject = new object
();
[STAThread]
static void Main( string
[] args)
{
Thread thread
= new Thread( new
ThreadStart(Do));
thread.Name
= " Thread1 "
;
Thread thread2
= new Thread( new
ThreadStart(Do));
thread2.Name
= " Thread2 "
;
thread.Start();
thread2.Start();
thread.Join();
thread2.Join();
Console.Read();
}
static void
Do()
{
if ( !
Monitor.TryEnter(m_monitorObject))
{
Console.WriteLine(
" Can't visit Object " +
Thread.CurrentThread.Name);
return
;
}
try

{
Monitor.Enter(m_monitorObject);
Console.WriteLine(
" Enter Monitor " + Thread.CurrentThread.Name);
Thread.Sleep(
5000
);
}
finally

{
Monitor.Exit(m_monitorObject);
}
}

  當線程1獲取了m_monitorObject對象獨佔權時,線程2嘗試調用TryEnter(m_monitorObject),此時會由於無法獲取獨佔權而返回false,輸出信息如下:

  另外,Monitor還提供了三個靜態方法Monitor.Pulse(Object o),Monitor.PulseAll(Object o)和Monitor.Wait(Object o ) ,用來實現一種喚醒機制的同步。關於這三個方法的用法,可以參考MSDN,這裏就不詳述了。

  五、Mutex

  在使用上,Mutex與上述的Monitor比較接近,不過Mutex不具備Wait,Pulse,PulseAll的功能,因此,我們不能使用Mutex實現類似的喚醒的功能。不過Mutex有一個比較大的特點,Mutex是跨進程的,因此我們可以在同一臺機器甚至遠程的機器上的多個進程上使用同一個互斥體。儘管Mutex也可以實現進程內的線程同步,而且功能也更強大,但這種情況下,還是推薦使用Monitor,因爲Mutex類是win32封裝的,所以它所需要的互操作轉換更耗資源。

  六、ReaderWriterLock

  在考慮資源訪問的時候,慣性上我們會對資源實施lock機制,但是在某些情況下,我們僅僅需要讀取資源的數據,而不是修改資源的數據,在這種情況下獲取資源的獨佔權無疑會影響運行效率,因此.Net提供了一種機制,使用ReaderWriterLock進行資源訪問時,如果在某一時刻資源並沒有獲取寫的獨佔權,那麼可以獲得多個讀的訪問權,單個寫入的獨佔權,如果某一時刻已經獲取了寫入的獨佔權,那麼其它讀取的訪問權必須進行等待,參考以下代碼:

Code
private static ReaderWriterLock m_readerWriterLock = new
ReaderWriterLock();
private static int m_int = 0
;
[STAThread]
static void Main(string
[] args)
{
Thread readThread
= new Thread(new
ThreadStart(Read));
readThread.Name
= "ReadThread1"
;
Thread readThread2
= new Thread(new
ThreadStart(Read));
readThread2.Name
= "ReadThread2"
;
Thread writeThread
= new Thread(new
ThreadStart(Writer));
writeThread.Name
= "WriterThread"
;
readThread.Start();
readThread2.Start();
writeThread.Start();
readThread.Join();
readThread2.Join();
writeThread.Join();

Console.ReadLine();
}
private static void
Read()
{
while (true
)
{
Console.WriteLine(
"ThreadName " + Thread.CurrentThread.Name + " AcquireReaderLock"
);
m_readerWriterLock.AcquireReaderLock(
10000
);
Console.WriteLine(String.Format(
"ThreadName : {0} m_int : {1}"
, Thread.CurrentThread.Name, m_int));
m_readerWriterLock.ReleaseReaderLock();
}
}

private static void
Writer()
{
while (true
)
{
Console.WriteLine(
"ThreadName " + Thread.CurrentThread.Name + " AcquireWriterLock"
);
m_readerWriterLock.AcquireWriterLock(
1000
);
Interlocked.Increment(
ref
m_int);
Thread.Sleep(
5000
);
m_readerWriterLock.ReleaseWriterLock();
Console.WriteLine(
"ThreadName " + Thread.CurrentThread.Name + " ReleaseWriterLock"
);
}
}

在程序中,我們啓動兩個線程獲取m_int的讀取訪問權,使用一個線程獲取m_int的寫入獨佔權,執行代碼後,輸出如下:

可以看到,當WriterThread獲取到寫入獨佔權後,任何其它讀取的線程都必須等待,直到WriterThread釋放掉寫入獨佔權後,才能獲取到數據的訪問權,應該注意的是,上述打印信息很明顯顯示出,可以多個線程同時獲取數據的讀取權,這從ReadThread1和ReadThread2的信息交互輸出可以看出。

  七、SynchronizationAttribute

  當我們確定某個類的實例在同一時刻只能被一個線程訪問時,我們可以直接將類標識成Synchronization的,這樣,CLR會自動對這個類實施同步機制,實際上,這裏面涉及到同步域的概念,當類按如下設計時,我們可以確保類的實例無法被多個線程同時訪問
  1). 在類的聲明中,添加System.Runtime.Remoting.Contexts.SynchronizationAttribute屬性。
    2). 繼承至System.ContextBoundObject
    需要注意的是,要實現上述機制,類必須繼承至System.ContextBoundObject,換句話說,類必須是上下文綁定的。
    一個示範類代碼如下:

Code
[System.Runtime.Remoting.Contexts.Synchronization]
public class
SynchronizedClass : System.ContextBoundObject
{

}

 

  八、MethodImplAttribute

  如果臨界區是跨越整個方法的,也就是說,整個方法內部的代碼都需要上鎖的話,使用MethodImplAttribute屬性會更簡單一些。這樣就不用在方法內部加鎖了,只需要在方法上面加上 [MethodImpl(MethodImplOptions.Synchronized)] 就可以了,MehthodImpl和MethodImplOptions都在命名空間System.Runtime.CompilerServices 裏面。但要注意這個屬性會使整個方法加鎖,直到方法返回,才釋放鎖。因此,使用上不太靈活。如果要提前釋放鎖,則應該使用Monitor或lock。我們來看一個例子:

Code
[MethodImpl(MethodImplOptions.Synchronized)]
public void
DoSomeWorkSync()
{
Console.WriteLine(
" DoSomeWorkSync() -- Lock held by Thread " +

Thread.CurrentThread.GetHashCode());
Thread.Sleep(
1000 );
Console.WriteLine(
" DoSomeWorkSync() -- Lock released by Thread " +

Thread.CurrentThread.GetHashCode());
}
public void DoSomeWorkNoSync()
{
Console.WriteLine(
" DoSomeWorkNoSync() -- Entered Thread is " +

Thread.CurrentThread.GetHashCode());
Thread.Sleep(
1000 );
Console.WriteLine(
" DoSomeWorkNoSync() -- Leaving Thread is " +

Thread.CurrentThread.GetHashCode());
}

[STAThread]
static void Main( string [] args)
{
MethodImplAttr testObj
= new
MethodImplAttr();
Thread t1
= new Thread( new
ThreadStart(testObj.DoSomeWorkNoSync));
Thread t2
= new Thread( new
ThreadStart(testObj.DoSomeWorkNoSync));
t1.Start();
t2.Start();
Thread t3
= new Thread( new
ThreadStart(testObj.DoSomeWorkSync));
Thread t4
= new Thread( new
ThreadStart(testObj.DoSomeWorkSync));
t3.Start();
t4.Start();

Console.ReadLine();
}

這裏,我們有兩個方法,我們可以對比一下,一個是加了屬性MethodImpl的DoSomeWorkSync(),一個是沒加的DoSomeWorkNoSync()。在方法中Sleep(1000)是爲了在第一個線程還在方法中時,第二個線程能夠有足夠的時間進來。對每個方法分別起了兩個線程,我們先來看一下結果:

可以看出,對於線程1和2,也就是調用沒有加屬性的方法的線程,當線程2進入方法後,還沒有離開,線程1有進來了,這就是說,方法沒有同步。我們再來看看線程3和4,當線程3進來後,方法被鎖,直到線程3釋放了鎖以後,線程4才進來。

  九、同步事件和等待句柄

  用lock和Monitor可以很好地起到線程同步的作用,但它們無法實現線程之間傳遞事件。如果要實現線程同步的同時,線程之間還要有交互,就要用到同步事件。同步事件是有兩個狀態(終止和非終止)的對象,它可以用來激活和掛起線程。

  同步事件有兩種:AutoResetEventManualResetEvent。它們之間唯一不同的地方就是在激活線程之後,狀態是否自動由終止變爲非終止。AutoResetEvent自動變爲非終止,就是說一個AutoResetEvent只能激活一個線程。而ManualResetEvent要等到它的Reset方法被調用,狀態才變爲非終止,在這之前,ManualResetEvent可以激活任意多個線程。

  可以調用WaitOne、WaitAny或WaitAll來使線程等待事件。它們之間的區別可以查看MSDN。當調用事件的 Set方法時,事件將變爲終止狀態,等待的線程被喚醒。

  來看一個例子,這個例子是MSDN上的。因爲事件只用於一個線程的激活,所以使用 AutoResetEventManualResetEvent 類都可以。

Code
static
AutoResetEvent autoEvent;

static void
DoWork()
{
Console.WriteLine(
" worker thread started, now waiting on event"
);
autoEvent.WaitOne();
Console.WriteLine(
" worker thread reactivated, now exiting"
);
}

[STAThread]
static void Main(string
[] args)
{
autoEvent
= new AutoResetEvent(false
);

Console.WriteLine(
"main thread starting worker thread"
);
Thread t
= new Thread(new
ThreadStart(DoWork));
t.Start();

Console.WriteLine(
"main thrad sleeping for 1 second"
);
Thread.Sleep(
1000
);

Console.WriteLine(
"main thread signaling worker thread"
);
autoEvent.Set();

Console.ReadLine();
}

我們先來看一下輸出:

在主函數中,首先創建一個AutoResetEvent的實例,參數false表示初始狀態爲非終止,如果是true的話,初始狀態則爲終止。然後創建並啓動一個子線程,在子線程中,通過調用AutoResetEvent的WaitOne方法,使子線程等待指定事件的發生。然後主線程等待一秒後,調用AutoResetEvent的Set方法,使狀態由非終止變爲終止,重新激活子線程。

 

 

參考:

1/MSDN(http://msdn.microsoft.com/zh-cn/library/ms173179(VS.80).aspx )

2/http://www.cnblogs.com/VincentWP/archive/2008/06/25/1229104.html

(如下文所示)

 

在現代的程序開發中,資源的同步是一個比較重要的課題,在.Net中,對這部分有很豐富類庫供我們使用,現在總結一下在各種情況下對資源同步的
機制。

     1.將字段聲明爲volatile
       
        當一個字段被聲明爲volatile時,CLR中一些管理代碼和內存的內部機制將負責對字段進行同步,並且總能保證讀取到的字段信息都爲最新的值,被聲明爲
volatile的字段必須具備以下特徵之一
         
        1.爲引用類型
        2.一個指針(在不安全代碼中)
        3.sbyte,byte,short,ushort,int,uint,char,float,bool
        4.一個使用底層類型的枚舉類型

     2.使用System.Threading.Interlocked 類

         在許多增對整數的操作中,我們都比較容易忽視線程的問題,例如執行下列代碼

         i = i + 1;

         實際上,上述代碼分爲3步驟
              1).  從內存中,讀取i的值
              2).  將讀取出來的值加1
              3).  將新的值寫入內存中

         在單線程上,這個操作不會有任何問題,但是當i被多個線程訪問時,問題就出現了,對i進行修改的線程,在上述的任何一部都有可能被其它讀取線程打斷,想象一下,
當操作線程執行完第二步,準備將新的值寫入內存中時,此時其它讀取線程獲得了執行權,這時讀取到的i的值並不是我們想要的,因此,這個操作不具備原子性,在.Net中,使用Interlocked類能確保操作的原子性,Interlocked類有以下的方法

          Increment
          Decrement
          Exchange
         
         上述的方法的參數都爲帶ref 標識的參數,因此我們說,這些方法保證了數據的原子性,在多線程中,涉及到整數的操作時,數據的原子性值得考慮,Interlocked的操作代碼如下

            int i = 0;
            System.Threading.Interlocked.Increment(
ref i);
            Console.WriteLine(i);
            System.Threading.Interlocked.Decrement(
ref i);
            Console.WriteLine(i);
            System.Threading.Interlocked.Exchange(
ref i, 100);
            Console.WriteLine(i);

          輸出信息如下

          

       3.使用lock關鍵字
            
          地球人都知道,使用lock關鍵字可以獲取一個對象的獨佔權,任何需要獲取這個對象的操作的線程必須等待以獲取該對象的線程釋放獨佔權,lock提供了簡單的同步資源的方法,與Java中的synchronized關鍵字類似。

          lock關鍵字的使用示例代碼如下
object o = new object();
            
lock (o)
            
{
                Console.WriteLine(
"O");
            }


       4.使用System.Theading.Monitor類進行同步

          System.Threading.Monitor類提供了與lock類似的功能,不過與lock不同的是,它能更好的控制同步塊,當調用了Monitor的Enter(Object o)方法時,會獲取o的獨佔權,直到調用Exit(Object o)方法時,纔會釋放對o的獨佔權,可以多次調用Enter(Object o)方法,只需要調用同樣次數的Exit(Object o)方法即可,Monitor類同時提供了TryEnter(Object o,[int])的一個重載方法,該方法嘗試獲取o對象的獨佔權,當獲取獨佔權失敗時,將返回false,查看如下代碼

          
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace MonitorApplication
{
    
class Program
    
{
        
private static object m_monitorObject = new object();
        
static void Main(string[] args)
        
{
            Thread thread 
= new Thread(Do);
            thread.Name 
= "Thread1";
            Thread thread2 
= new Thread(Do);
            thread2.Name 
= "Thread2";
            thread.Start();
            thread2.Start();
            thread.Join();
            thread2.Join();

        }


        
static void Do()
        
{
            
if (!Monitor.TryEnter(m_monitorObject))
            
{
                Console.WriteLine(
"Can't visit Object " + Thread.CurrentThread.Name);
                
return;
            }

            
try
            
{
                Monitor.Enter(m_monitorObject);
                Console.WriteLine(
"Enter Monitor " + Thread.CurrentThread.Name);
                Thread.Sleep(
5000);
            }

            
finally
            
{
                Monitor.Exit(m_monitorObject);
            }

        }

    }

}



 當線程1獲取了m_monitorObject對象獨佔權時,線程2嘗試調用TryEnter(m_monitorObject),此時會由於無法獲取獨佔權而返回false,輸出信息如下



可以看到線程2無法獲取到m_monitorObject的獨佔權,因此輸出了一條錯誤信息.

Monitor類比lock類提供了一種更優秀的功能,考慮一下如下的場景

      1.當你進入某家餐館時,發現餐館裏坐滿了客人,但是你又不想換地方,因此,你選擇等待。
      2.當餐館內的服務員發現有空位置時,他通知前臺的服務生,服務生給你安排了一個座位,於是你開始享受國際化的大餐。

使用Monitor類就可以實現如上的應用需求,在Monitor類中,提供瞭如下三個靜態方法

       Monitor.Pulse(Object o)
       Monitor.PulseAll(Object o)
       Monitor.Wait(Object o ) // 重載函數

當調用Wait方法時,線程釋放資源的獨佔鎖,並且阻塞在wait方法直到另外的線程獲取資源的獨佔鎖後,更新資源信息並調用Pulse方法後返回。

我們模擬上述代碼如下

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace MonitorApplication
{
    
class Program
    
{
        
private static  object m_isFull = new object();
        
static void Main(string[] args)
        
{
            Thread thread 
= new Thread(HaveLunch);
            thread.Name 
= "HaveLunchThread";
            Thread thread2 
= new Thread(SeatChange);
            thread2.Name 
= "SeatChangeThread";
            thread.Start();
            System.Threading.Thread.Sleep(
2000);
            thread2.Start();
            thread.Join();
            thread2.Join();
        }


        
private static void HaveLunch()
        
{
            
lock (m_isFull)
            
{
                Console.WriteLine(
"Wati for seta");
                Monitor.Wait(m_isFull);
                Console.WriteLine(
"Have a good lunch!");
            }

        }


        
private static void SeatChange()
        
{
            
lock (m_isFull)
            
{
                Console.WriteLine(
"Seat was changed");
                Monitor.Pulse(m_isFull);
            }

        }

    }

}



輸出信息如下



可見,使用Monitor,我們能實現一種喚醒式的機制,相信在實際應用中有不少類似的場景。


          5.使用System.Threading.Mutex(互斥體)類實現同步

          在使用上,Mutex與上述的Monitor比較接近,不過Mutex不具備Wait,Pulse,PulseAll的功能,因此,我們不能使用Mutex實現類似的喚醒的功能,不過Mutex有一個比較大的特點,Mutex是跨進程的,因此我們可以在同一臺機器甚至遠程的機器上的多個進程上使用同一個互斥體。

           考慮如下的代碼,代碼通過獲取一個稱爲ConfigFileMutex的互斥體,修改配置文件信息,寫入一條數據,我們同時開啓兩個相同的進程進行測試
         
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO;
using System.Diagnostics;
namespace MonitorApplication
{
    
class Program
    
{
        
static void Main(string[] args)
        
{
            Mutex configFileMutex 
= new Mutex(false"configFileMutex");
            Console.WriteLine(
"Wait for configFileMutex Process Id : " + Process.GetCurrentProcess().Id);
            configFileMutex.WaitOne();
            Console.WriteLine(
"Enter configFileMutex Process Id : " + Process.GetCurrentProcess().Id);
            System.Threading.Thread.Sleep(
10000);
            
if (!File.Exists(@"./config.txt"))
            
{
                FileStream stream 
= File.Create(@"./config.txt");
                StreamWriter writer 
= new StreamWriter(stream);
                writer.WriteLine(
"This is a Test!");
                writer.Close();
                stream.Close();
            }

            
else
            
{
                String[] lines 
= File.ReadAllLines(@"./config.txt");
                
for (int i = 0; i < lines.Length; i++)
                    Console.WriteLine(lines[i]);
            }

            configFileMutex.ReleaseMutex();
            configFileMutex.Close();
        }

    }

}


           
運行截圖如下



此時,PID 爲 4628的進程正在等待PID 爲 3216的進程釋放configFileMutex的互斥體,因此它阻塞在WaitOne函數中,當PID爲3216的進程添加並寫入了一條信息
至config.txt文件後,釋放configFileMutex的獨佔權,PID爲4628的進程獲取configFileMutex的獨佔權,並從config.txt文件中讀取輸出PID3216進程寫入的信息,截圖如下


可見使用Mutex進行同步,同步的互斥體是存在於多個進程間的。


            6. 使用System.Threading.ReaderWriterLock類實現多用戶讀/單用戶寫的同步訪問機制

                在考慮資源訪問的時候,慣性上我們會對資源實施lock機制,但是在某些情況下,我們僅僅需要讀取資源的數據,而不是修改資源的數據,在這種情況下獲取資源的獨佔權無疑會影響運行效率,因此.Net提供了一種機制,使用ReaderWriterLock進行資源訪問時,如果在某一時刻資源並沒有獲取寫的獨佔權,那麼可以獲得多個讀的訪問權,單個寫入的獨佔權,如果某一時刻已經獲取了寫入的獨佔權,那麼其它讀取的訪問權必須進行等待,參考以下代碼


             
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO;
using System.Diagnostics;
namespace MonitorApplication
{
    
class Program
    
{
        
private static ReaderWriterLock m_readerWriterLock = new ReaderWriterLock();
        
private static int m_int = 0;
        
static void Main(string[] args)
        
{
            Thread readThread 
= new Thread(Read);
            readThread.Name 
= "ReadThread1";
            Thread readThread2 
= new Thread(Read);
            readThread2.Name 
= "ReadThread2";
            Thread writeThread 
= new Thread(Writer);
            writeThread.Name 
= "WriterThread";
            readThread.Start();
            readThread2.Start();
            writeThread.Start();
            readThread.Join();
            readThread2.Join();
            writeThread.Join();

        }


        
private static void Read()
        
{
            
while (true)
            
{
                Console.WriteLine(
"ThreadName " + Thread.CurrentThread.Name + " AcquireReaderLock");

                m_readerWriterLock.AcquireReaderLock(
10000);

                Console.WriteLine(String.Format(
"ThreadName : {0}  m_int : {1}", Thread.CurrentThread.Name, m_int));

                m_readerWriterLock.ReleaseReaderLock();
            }

        }


        
private static void Writer()
        
{
            
while (true)
            
{
                Console.WriteLine(
"ThreadName " + Thread.CurrentThread.Name + " AcquireWriterLock");

                m_readerWriterLock.AcquireWriterLock(
1000);

                Interlocked.Increment(
ref m_int);

                Thread.Sleep(
5000);

                m_readerWriterLock.ReleaseWriterLock();

                Console.WriteLine(
"ThreadName " + Thread.CurrentThread.Name + " ReleaseWriterLock");
            }

        }

    }

}


在程序中,我們啓動兩個線程獲取m_int的讀取訪問權,使用一個線程獲取m_int的寫入獨佔權,執行代碼後,輸出如下



可以看到,當WriterThread獲取到寫入獨佔權後,任何其它讀取的線程都必須等待,直到WriterThread釋放掉寫入獨佔權後,才能獲取到數據的訪問權,
應該注意的是,上述打印信息很明顯顯示出,可以多個線程同時獲取數據的讀取權,這從ReadThread1和ReadThread2的信息交互輸出可以看出。


         7.使用System.Runtime.Remoting.Contexts.SynchronizationAttribute對類對象進行同步控制

            當我們確定某個類的實例在同一時刻只能被一個線程訪問時,我們可以直接將類標識成Synchronization的,這樣,CLR會自動對這個類實施同步機制,
實際上,這裏面涉及到同步域的概念,當類按如下設計時,我們可以確保類的實例無法被多個線程同時訪問

                      1). 在類的聲明中,添加System.Runtime.Remoting.Contexts.SynchronizationAttribute屬性。
                      2). 繼承至System.ContextBoundObject

            需要注意的是,要實現上述機制,類必須繼承至System.ContextBoundObject,換句話說,類必須是上下文綁定的.

            一個示範類代碼如下

           
 [System.Runtime.Remoting.Contexts.Synchronization]
 
public class SynchronizedClass : System.ContextBoundObject
 
{

 }



            還有AutoResetEvent,ManualReset,EventWaitHandle,Semaphore等可以實現資源的控制,不過它們更多是是基於一種事件喚醒的機制,如果有興趣可以查閱MSDN相關的文檔。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章