設計模式の委派模式&策略模式
引用網址:https://www.jb51.net/article/198572.htm
Mutex類、Event類、SemaphoreSlim類和ReaderWriterLockSlim類等提供了多個程序之間的執行緒同步。
1、WaitHandle 基類
WaitHandle抽象類,用於等待一個訊號的設定。可以根據其派生類的不同,等待不同的訊號。非同步委託的BeginInvoke()方法返回一個實現了IAsycResult介面的物件。使用IAsycResult介面可以用AsycWaitHandle屬性訪問WaitHandle基類。在呼叫WaitOne()方法時,執行緒會等待接收一個和等待控制代碼相關的訊號:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
static void Main( string [] args)
{
Func< int > func = new Func< int >(
() =>
{
Thread.Sleep(1500);
return 1;
});
IAsyncResult ar = func.BeginInvoke( null , null );
int count = 0;
while ( true )
{
Interlocked.Increment( ref count);
Console.WriteLine( "第{0}週期迴圈等待結果。" , count);
if (ar.AsyncWaitHandle.WaitOne(100, false ))
{
Console.WriteLine( "獲得返回結果。" );
break ;
}
}
int result = func.EndInvoke(ar);
Console.WriteLine( "結果為:{0}" , result); }
|
使用WaitHandle基類可以等待一個訊號的出現(WaitHandle()方法)、等待多個物件都必須發出訊號(WaitAll()方法)、等待多個物件中任一一個發出訊號(WaitAny()方法)。其中WaitAll()方法和WaitAny()方法時WaitHandle類的靜態方法,接收一個WaitHandle引數陣列。
WaitHandle基類的SafeWaitHandle屬性,其中可以將一個本機控制代碼賦予一個系統資源,等待該控制代碼,如I/O操作,或者自定義的控制代碼。
2、Mutex 類
Mutex類繼承自WaitHandle類,提供跨多個程序同步訪問的一個類。類似於Monitor類,只能有一個執行緒擁有鎖定。在Mutex類的建構函式各引數含義:
- initiallyOwned: 如果為 true,則給予呼叫執行緒已命名的系統互斥體的初始所屬權(如果已命名的系統互斥體是通過此呼叫建立的);否則為 false。
- name:系統互斥體的名稱。 如果值為 null,則 System.Threading.Mutex 是未命名的。
- createdNew: 在此方法返回時,如果建立了局部互斥體(即,如果 name 為 null 或空字串)或指定的命名系統互斥體,則包含布林值 true;如果指定的命名系統互斥體已存在,則為false。 該引數未經初始化即被傳遞。
- mutexSecurity: 一個 System.Security.AccessControl.MutexSecurity 物件,表示應用於已命名的系統互斥體的訪問控制安全性。
互斥也可以在另一個程序中定義,作業系統能夠識別有名稱的互斥,它由程序之間共享。如果沒有指定互斥的名稱,則不在不同的程序之間共享。該方法可以檢測程式是否已執行,可以禁止程式啟動兩次。
1 2 3 4 5 6 7 8 9 10 11 |
static void Main( string [] args)
{
// ThreadingTimer();
// TimersTimer();
bool isCreateNew = false ;
Mutex mutex = new Mutex( false , "MyApp" , out isCreateNew); //查詢是否已有互斥“MyApp”存在
if (isCreateNew== false )
{
//已存在互斥
}
}
|
要開啟已有互斥,可以使用Mutex.OpenExisting()方法,不需要建構函式建立互斥時需要的相同.Net許可權。可以使用WaitOne()方法獲得互斥的鎖定,成為該互斥的擁有著。呼叫ReleaseMutex()方法釋放互斥:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
if (mutex.WaitOne()) //設定互斥鎖定
{
try
{
//執行程式碼
}
finally {
mutex.ReleaseMutex(); //釋放互斥
}
}
else
{
//出現問題
}
|
3、Semaphore 類
訊號量是一種計數的互斥鎖定,可以同時由多個執行緒使用。訊號量可定義允許同時訪問受旗語鎖定保護的資源的執行緒個數。Semaphore和SemaphoreSlim兩個類具有訊號量功能。Semaphore類可以指定名稱,讓其在系統資源範圍內查詢到,允許在不同的程序之間同步。Semaphore類是對較短等待時間進行優化了的輕型版本。
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
static void Main()
{
int taskCount = 6;
int semaphoreCount = 3;
Semaphore semaphore = new Semaphore(semaphoreCount, semaphoreCount, "Test" ); //建立計數為3的訊號量
/* 第一個引數為初始釋放的鎖定數,第二個引數為可鎖定的總數。如果第一個引數小於第二個引數,其差值就是已分配執行緒的計量數。
* 第三個引數為訊號指定的名稱,能讓它在不同的程序之間共享。
*/
var tasks = new Task[taskCount];
for ( int i = 0; i < taskCount; i++)
{
tasks[i] = Task.Run(() => TaskMain(semaphore)); //建立6個任務
}
Task.WaitAll(tasks);
Console.WriteLine( "All tasks finished" );
}
//鎖定訊號的任務
static void TaskMain(Semaphore semaphore)
{
bool isCompleted = false ;
while (!isCompleted) //迴圈等待被釋放的訊號量
{
if (semaphore.WaitOne(600)) //最長等待600ms
{
try
{
Console.WriteLine( "Task {0} locks the semaphore" , Task.CurrentId);
Thread.Sleep(2000); //2s後釋放訊號
}
finally
{
Console.WriteLine( "Task {0} releases the semaphore" , Task.CurrentId);
semaphore.Release(); //釋放訊號量
isCompleted = true ;
}
}
else
{
//超過規定的等待時間,寫入一條超時等待的資訊
Console.WriteLine( "Timeout for task {0}; wait again" , Task.CurrentId);
}
}
}
|
以上方法中,訊號量計數為3,因此最多隻有三個任務可獲得鎖定,第4個及以後的任務必須等待。在解除鎖定時,任何情況下一定要解除資源的鎖定。
4、Events 類
事件也是一個系統範圍內資源同步的方法。主要由以下幾個類提供:ManualResetEvent、AutoResetEvent、ManualResetEventSlim、和CountdownEvent類。
ManualResetEventSlim類中,呼叫Set()方法可以發出訊號;呼叫Reset()方法可以使重置為無訊號狀態。如果多個執行緒在等待向一個事件發出訊號,並呼叫Set()方法,就釋放所有等待執行緒。如果一個執行緒剛剛呼叫了WiatOne()方法,但事件已發出訊號,等待的執行緒就可以繼續等待。
AutoResetEvent類中,同樣可以通過Set()方法發出訊號、Reset()方法重置訊號,但是該類是自動重置訊號。如果一個執行緒在等待自動重置的事件發訊號,當第一個執行緒的等待狀態結束時,該事件會自動變為不發訊號的狀態。即:如果多個執行緒在等待向事件發訊號,只有一個執行緒結束其等待狀態,它不是等待事件最長的執行緒,而是優先順序最高的執行緒。
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
//計算資料的類,使用ManualResetEventSlim類的示例
public class Calculator
{
private ManualResetEventSlim mEvent;
public int Result { get ; private set ; }
public Calculator(ManualResetEventSlim ev)
{
this .mEvent = ev;
}
public void Calculation( int x, int y)
{
Console.WriteLine( "Task {0} starts calculation" , Task.CurrentId);
Thread.Sleep( new Random().Next(3000)); //隨機等待事件
Result = x + y; //計算結果
Console.WriteLine( "Task {0} is ready" , Task.CurrentId);
mEvent.Set(); //發出完成訊號
}
}
//外部呼叫的示例:
static void Main()
{
const int taskCount = 10;
ManualResetEventSlim[] mEvents = new ManualResetEventSlim[taskCount];
WaitHandle[] waitHandles = new WaitHandle[taskCount];
var calcs = new Calculator[taskCount];
for ( int i = 0; i < taskCount; i++)
{
int i1 = i; //目的是使後面要執行的Task不必等待執行完後才釋放i,讓for繼續
mEvents[i] = new ManualResetEventSlim( false ); //對應任務的事件物件發出訊號
waitHandles[i] = mEvents[i].WaitHandle; //ManualResetEvent類派生自WaitHandle類,但ManualResetEventSlim並不是,因此需要儲存其WaitHandle物件
calcs[i] = new Calculator(mEvents[i]);
Task.Run(() => calcs[i1].Calculation(i1 + 1, i1 + 3));
}
for ( int i = 0; i < taskCount; i++)
{
int index = WaitHandle.WaitAny(waitHandles); //等待任何一個發出訊號,並返回發出訊號的索引
if (index == WaitHandle.WaitTimeout)
{
Console.WriteLine( "Timeout!!" );
}
else
{
mEvents[index].Reset(); //重新設定為無訊號狀態
Console.WriteLine( "finished task for {0}, result: {1}" , index, calcs[index].Result);
}
}
}
|
CountdownEvent類適用於:需要把一個工作任務分配到多個任務中,然後在各個任務結束後合併結果(不需要為每個任務單獨建立事件物件)。每個任務不需要同步。CountdownEvent類為所有設定了事件的任務定義了一個初始數字,達到該計數後,就發出訊號。
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
//修改計算類
public class Calculator
{
private CountdownEvent cEvent;
public int Result { get ; private set ; }
public Calculator(CountdownEvent ev)
{
this .cEvent = ev;
}
public void Calculation( int x, int y)
{
Console.WriteLine( "Task {0} starts calculation" , Task.CurrentId);
Thread.Sleep( new Random().Next(3000)); //隨機等待事件
Result = x + y; //計算結果
// signal the event—completed!
Console.WriteLine( "Task {0} is ready" , Task.CurrentId);
cEvent.Signal(); //發出完成訊號
}
}
//修改方法呼叫
static void Main()
{
const int taskCount = 10;
CountdownEvent cEvent = new CountdownEvent(taskCount);
WaitHandle[] waitHandles = new WaitHandle[taskCount];
var calcs = new Calculator[taskCount];
for ( int i = 0; i < taskCount; i++)
{
int i1 = i; //目的是使後面要執行的Task不必等待執行後才釋放i,讓for可以繼續
calcs[i] = new Calculator(cEvent); //為每個任務都賦該事件通知類
Task.Run(() => calcs[i1].Calculation(i1 + 1, i1 + 3));
}
cEvent.Wait(); //等待一個事件的訊號
Console.WriteLine( "all finished" );
for ( int i = 0; i < taskCount; i++)
{
Console.WriteLine( "task for {0}, result: {1}" , i, calcs[i].Result);
}
}
|
5、Barrier 類
Barrier類適用於:工作有多個任務分支,並且在所有任務執行完後需要合併的工作情況。與CountdownEvent不同於,該類用於需要同步的參與者。在啟用一個任務後,可以動態的新增其他參與者。在主參與者繼續之前,可以等待所有其他參與者完成工作。
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
static void Main()
{
const int numberTasks = 2;
const int partitionSize = 1000000;
var data = new List< string >(FillData(partitionSize * numberTasks));
var barrier = new Barrier(numberTasks + 1); //定義三個參與者:一個主參與者(分配任務者),兩個子參與者(被分配任務者)
var tasks = new Task< int []>[numberTasks]; //兩個子參與者
for ( int i = 0; i < numberTasks; i++)
{
int jobNumber = i;
tasks[i] = Task.Run(() => CalculationInTask(jobNumber, partitionSize, barrier, data)); //啟動計算任務:可以分開寫,以執行多個不同的任務。
}
barrier.SignalAndWait(); // 主參與者以完成,等待子參與者全部完成。
//合併兩個結果(LINQ)
IEnumerable< int > resultCollection = tasks[0].Result.Zip(tasks[1].Result, (c1, c2) => { return c1 + c2; }).ToList(); //立即求和
char ch = 'a' ;
int sum = 0;
foreach (var x in resultCollection)
{
Console.WriteLine( "{0}, count: {1}" , ch++, x); //輸出結果
sum += x;
}
Console.WriteLine( "main finished {0}" , sum); //統計過的字串數量
Console.WriteLine( "remaining {0}, phase {1}" , barrier.ParticipantsRemaining, barrier.CurrentPhaseNumber); //當前參與者資訊
}
static int [] CalculationInTask( int jobNumber, int partitionSize, Barrier barrier, IList< string > coll)
{
var data = new List< string >(coll);
int start = jobNumber * partitionSize; //計算其實下標
int end = start + partitionSize; //計算結束的位置
Console.WriteLine( "Task {0}: partition from {1} to {2}" , Task.CurrentId, start, end);
int [] charCount = new int [26];
for ( int j = start; j < end; j++)
{
char c = data[j][0]; //獲取當前字串的第一個字元
charCount[c - 97]++; //對應字元的數量+1;
}
Console.WriteLine( "Calculation completed from task {0}. {1} times a, {2} times z" , Task.CurrentId, charCount[0], charCount[25]); //告知計算完成
barrier.RemoveParticipant(); //告知,減少一個引數者
Console.WriteLine( "Task {0} removed from barrier, remaining participants {1}" , Task.CurrentId, barrier.ParticipantsRemaining);
return charCount; //返回統計的結果
}
//用於填充一個字串連結串列
public static IEnumerable< string > FillData( int size)
{
var data = new List< string >(size);
var r = new Random();
for ( int i = 0; i < size; i++)
{
data.Add(GetString(r)); //獲得一個隨機的字串
}
return data;
}
private static string GetString(Random r)
{
var sb = new StringBuilder(6);
for ( int i = 0; i < 6; i++)
{
sb.Append(( char )(r.Next(26) + 97)); //建立一個6個字元的隨機字串
}
return sb.ToString();
}
|
6、ReaderWriterLockSlim 類
該類是使鎖定機制允許鎖定多個讀取器(而不是一個寫入器)訪問某個資源:如果沒有寫入器鎖定資源,那麼允許多個讀取器訪問資源,但只能有一個寫入器鎖定該資源。
由它的屬性可以讀取是否處於堵塞或不堵塞的鎖定,如EnterReadLock()和TryEnterReadLock()方法。也可以獲得其是否處於寫入鎖定或非鎖定狀態,如EnterWriteLock()和TryEnterWriteLock()方法。如果任務需要先讀取資源,之後寫入資源,可以使用EnterUpgradeableReadLock()或TryEnterUpgradeableReadLock()方法獲取可升級的讀取鎖定。該鎖定可以獲取寫入鎖定,而不需要釋放讀取鎖定。
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
class Program
{
private static List< int > items = new List< int >() { 0, 1, 2, 3, 4, 5 };
private static ReaderWriterLockSlim rwl = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
static void ReaderMethod( object reader)
{
try
{
rwl.EnterReadLock();
for ( int i = 0; i < items.Count; i++)
{
Console.WriteLine( "reader {0}, loop: {1}, item: {2}" , reader, i, items[i]);
Thread.Sleep(40);
}
}
finally
{
rwl.ExitReadLock();
}
}
static void WriterMethod( object writer)
{
try
{
while (!rwl.TryEnterWriteLock(50))
{
Console.WriteLine( "Writer {0} waiting for the write lock" , writer);
Console.WriteLine( "current reader count: {0}" , rwl.CurrentReadCount);
}
Console.WriteLine( "Writer {0} acquired the lock" , writer);
for ( int i = 0; i < items.Count; i++)
{
items[i]++;
Thread.Sleep(50);
}
Console.WriteLine( "Writer {0} finished" , writer);
}
finally
{
rwl.ExitWriteLock();
}
}
static void Main()
{
var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);
var tasks = new Task[6];
tasks[0] = taskFactory.StartNew(WriterMethod, 1);
tasks[1] = taskFactory.StartNew(ReaderMethod, 1);
tasks[2] = taskFactory.StartNew(ReaderMethod, 2);
tasks[3] = taskFactory.StartNew(WriterMethod, 2);
tasks[4] = taskFactory.StartNew(ReaderMethod, 3);
tasks[5] = taskFactory.StartNew(ReaderMethod, 4);
for ( int i = 0; i < 6; i++)
{
tasks[i].Wait();
}
}
}
|
以上就是c# 程序之間的執行緒同步的詳細內容,更多關於c# 執行緒同步的資料請關注指令碼之家其它相關文章!
您可能感興趣的文章: