程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> C#線程資源同步方式總結

C#線程資源同步方式總結

編輯:關於C#

在現代的程序開發中,資源的同步是一個比較重要的課題,在.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]
publicclassSynchronizedClass:System.ContextBoundObject
{
}

還有AutoResetEvent,ManualReset,EventWaitHandle,Semaphore等可以實現資源的控制,不過它們更多是是基於一種事件喚醒的機制, 如果有興趣可以查閱MSDN相關的文檔。

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