ReaderWriterLock測試及自動釋放

在進行多執行緒模式的程式開發時,常常會需要針對會共同存取到的資料來進行同步化的動作。

同步化的方式,最常用到的應該是就Critical Section的方式了。

在C#中,要實作Critical Section的模式的話,可以使用Monitor類別,或是使用C#內建的關鍵字:lock。

不過,使用Critical Section模式的話,表示在任何一個時間下,只有一個執行緒可以來存取這項共用的資源。如果多數的執行緒只是要讀取其中的資料的話,不好意思,大家必項排隊等候這項資源才行。如果有這種情況發生的話,那多執行緒的程式設計方式,將無法為您的程式執行效能帶來太多的改善。因為,每個執行緒還是會需要去等候某項資源。

為了解決這個問題,C#中提供了一個ReaderWriterLock的物件。這種物件的特性就是在同一時間下,可以同時有多個執行緒來讀取某項共用資源。而當有其它的執行緒想要去寫入這項資源時,它也必須先排隊等候這項資源。一旦要寫入的執行緒取得資源的存取權限時,其它需要取得唯讀權限的執行緒,將全部被阻擋在外。要等到執行寫入資源的執行緒完成後,其它唯讀的執行緒才能再度取得資源。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace TestVS2008
{
public class TestReadWriteLock
{
protected bool isStop = false;
protected ReaderWriterLock rwLock = new ReaderWriterLock();
public void StartTest()
{
isStop = false;

for (int i = 0; i < 4; i++)
{
Thread t1 = new Thread(new ThreadStart(ReaderFunc));
t1.IsBackground = true;
t1.Start();
}

Thread t2 = new Thread(new ThreadStart(WriterFunc));
t2.IsBackground = true;
t2.Start();

Console.Read();

isStop = true;

}

//protected void ReaderFunc()
//{
// int threadID = Thread.CurrentThread.ManagedThreadId;
// while (!isStop)
// {
// Console.WriteLine("Thread " + threadID.ToString() + " R?");
// rwLock.AcquireReaderLock(Timeout.Infinite);

// Console.WriteLine("Thread " + threadID.ToString() + " R+");
// Thread.Sleep(2000);

// Console.WriteLine("Thread " + threadID.ToString() + " R-");
// rwLock.ReleaseReaderLock();

// }
//}

protected void ReaderFunc()
{
int threadID = Thread.CurrentThread.ManagedThreadId;
while (!isStop)
{
Console.WriteLine("Thread " + threadID.ToString() + " R?");
using (new ReaderLockWrapper(rwLock))
{
Console.WriteLine("Thread " + threadID.ToString() + " R+");
Thread.Sleep(2000);

Console.WriteLine("Thread " + threadID.ToString() + " R-");
}

}
}

protected void WriterFunc()
{
int threadID = Thread.CurrentThread.ManagedThreadId;
while (!isStop)
{
Console.WriteLine("Thread " + threadID.ToString() + " >>>>W?");
rwLock.AcquireWriterLock(Timeout.Infinite);

Console.WriteLine("Thread " + threadID.ToString() + " >>>>W+");
Thread.Sleep(2000);

Console.WriteLine("Thread " + threadID.ToString() + " >>>>W-");
rwLock.ReleaseWriterLock();
}
}
}

public class ReaderLockWrapper : IDisposable
{
protected ReaderWriterLock innerLock = null;

public ReaderLockWrapper(ReaderWriterLock crtLock)
{
innerLock = crtLock;

innerLock.AcquireReaderLock(Timeout.Infinite);
}

#region IDisposable 成員

public void Dispose()
{
int threadID = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Thread " + threadID.ToString() + " %%% Disposed");
if (innerLock != null)
{
innerLock.ReleaseReaderLock();
}
}

#endregion
}
}

在上面這段程式碼中,首先來看一下類別TestReadWriteLock的部份。在函式StartTest()中,我們利用迴圈的方式,產生了四個用來取得Reader權限的執行緒。而另外,在其後我們產生了一個用來取得Writer權限的執行緒。在執行後,可以確認當還有Reader權限的執行緒在讀取資源時,此時Writer權限不會配置給其它執行緒。只有當所有持有Reader權限的執行緒都釋放出資源時,此時Writer權限才會配置出去。

上面的ReaderWriterLock的使用方式大致上是沒有什麼問題了,只是每次都要用AcquireReaderLock()及ReleaseReaderLock()這樣成對的方式來使用,好像太繁鎖了。所以有人就想到使用using及IDisposable的方式來簡化這樣的步驟。程式碼如類別ReaderLockWrapper所示的。我們可以使用第二份ReaderFunc()中的方式來使用ReaderLockWrapper來實作。

留言

這個網誌中的熱門文章

DOS Batch指令檔中如何記錄log資訊

用捷徑方式執行需帶入命令列參數的Windows Form程式

使用regular expression來match中括號(square bracket)