c# 進程之間的線程同步
Mutex類、Event類、SemaphoreSlim類和ReaderWriterLockSlim類等提供了多個進程之間的線程同步。
1、WaitHandle 基類
WaitHandle抽象類,用于等待一個信號的設置。可以根據(jù)其派生類的不同,等待不同的信號。異步委托的BeginInvoke()方法返回一個實現(xiàn)了IAsycResult接口的對象。使用IAsycResult接口可以用AsycWaitHandle屬性訪問WaitHandle基類。在調(diào)用WaitOne()方法時,線程會等待接收一個和等待句柄相關的信號:
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}周期循環(huán)等待結果。", count);
if (ar.AsyncWaitHandle.WaitOne(100, false))
{
Console.WriteLine("獲得返回結果。");
break;
}
}
int result = func.EndInvoke(ar);
Console.WriteLine("結果為:{0}", result);
}
使用WaitHandle基類可以等待一個信號的出現(xiàn)(WaitHandle()方法)、等待多個對象都必須發(fā)出信號(WaitAll()方法)、等待多個對象中任一一個發(fā)出信號(WaitAny()方法)。其中WaitAll()方法和WaitAny()方法時WaitHandle類的靜態(tài)方法,接收一個WaitHandle參數(shù)數(shù)組。
WaitHandle基類的SafeWaitHandle屬性,其中可以將一個本機句柄賦予一個系統(tǒng)資源,等待該句柄,如I/O操作,或者自定義的句柄。
2、Mutex 類
Mutex類繼承自WaitHandle類,提供跨多個進程同步訪問的一個類。類似于Monitor類,只能有一個線程擁有鎖定。在Mutex類的構造函數(shù)各參數(shù)含義:
- initiallyOwned: 如果為 true,則給予調(diào)用線程已命名的系統(tǒng)互斥體的初始所屬權(如果已命名的系統(tǒng)互斥體是通過此調(diào)用創(chuàng)建的);否則為 false。
- name:系統(tǒng)互斥體的名稱。 如果值為 null,則 System.Threading.Mutex 是未命名的。
- createdNew: 在此方法返回時,如果創(chuàng)建了局部互斥體(即,如果 name 為 null 或空字符串)或指定的命名系統(tǒng)互斥體,則包含布爾值 true;如果指定的命名系統(tǒng)互斥體已存在,則為false。 該參數(shù)未經(jīng)初始化即被傳遞。
- mutexSecurity: 一個 System.Security.AccessControl.MutexSecurity 對象,表示應用于已命名的系統(tǒng)互斥體的訪問控制安全性。
互斥也可以在另一個進程中定義,操作系統(tǒng)能夠識別有名稱的互斥,它由進程之間共享。如果沒有指定互斥的名稱,則不在不同的進程之間共享。該方法可以檢測程序是否已運行,可以禁止程序啟動兩次。
static void Main(string[] args)
{
// ThreadingTimer();
// TimersTimer();
bool isCreateNew = false;
Mutex mutex = new Mutex(false, "MyApp", out isCreateNew);//查詢是否已有互斥“MyApp”存在
if(isCreateNew==false)
{
//已存在互斥
}
}
要打開已有互斥,可以使用Mutex.OpenExisting()方法,不需要構造函數(shù)創(chuàng)建互斥時需要的相同.Net權限??梢允褂肳aitOne()方法獲得互斥的鎖定,成為該互斥的擁有著。調(diào)用ReleaseMutex()方法釋放互斥:
if(mutex.WaitOne())//設置互斥鎖定
{
try
{
//執(zhí)行代碼
}
finally {
mutex.ReleaseMutex();//釋放互斥
}
}
else
{
//出現(xiàn)問題
}
3、Semaphore 類
信號量是一種計數(shù)的互斥鎖定,可以同時由多個線程使用。信號量可定義允許同時訪問受旗語鎖定保護的資源的線程個數(shù)。Semaphore和SemaphoreSlim兩個類具有信號量功能。Semaphore類可以指定名稱,讓其在系統(tǒng)資源范圍內(nèi)查找到,允許在不同的進程之間同步。Semaphore類是對較短等待時間進行優(yōu)化了的輕型版本。
static void Main()
{
int taskCount = 6;
int semaphoreCount = 3;
Semaphore semaphore = new Semaphore(semaphoreCount, semaphoreCount, "Test");//創(chuàng)建計數(shù)為3的信號量
/* 第一個參數(shù)為初始釋放的鎖定數(shù),第二個參數(shù)為可鎖定的總數(shù)。如果第一個參數(shù)小于第二個參數(shù),其差值就是已分配線程的計量數(shù)。
* 第三個參數(shù)為信號指定的名稱,能讓它在不同的進程之間共享。
*/
var tasks = new Task[taskCount];
for (int i = 0; i < taskCount; i++)
{
tasks[i] = Task.Run(() => TaskMain(semaphore));//創(chuàng)建6個任務
}
Task.WaitAll(tasks);
Console.WriteLine("All tasks finished");
}
//鎖定信號的任務
static void TaskMain(Semaphore semaphore)
{
bool isCompleted = false;
while (!isCompleted)//循環(huán)等待被釋放的信號量
{
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
{
//超過規(guī)定的等待時間,寫入一條超時等待的信息
Console.WriteLine("Timeout for task {0}; wait again", Task.CurrentId);
}
}
}
以上方法中,信號量計數(shù)為3,因此最多只有三個任務可獲得鎖定,第4個及以后的任務必須等待。在解除鎖定時,任何情況下一定要解除資源的鎖定。
4、Events 類
事件也是一個系統(tǒng)范圍內(nèi)資源同步的方法。主要由以下幾個類提供:ManualResetEvent、AutoResetEvent、ManualResetEventSlim、和CountdownEvent類。
ManualResetEventSlim類中,調(diào)用Set()方法可以發(fā)出信號;調(diào)用Reset()方法可以使重置為無信號狀態(tài)。如果多個線程在等待向一個事件發(fā)出信號,并調(diào)用Set()方法,就釋放所有等待線程。如果一個線程剛剛調(diào)用了WiatOne()方法,但事件已發(fā)出信號,等待的線程就可以繼續(xù)等待。
AutoResetEvent類中,同樣可以通過Set()方法發(fā)出信號、Reset()方法重置信號,但是該類是自動重置信號。如果一個線程在等待自動重置的事件發(fā)信號,當?shù)谝粋€線程的等待狀態(tài)結束時,該事件會自動變?yōu)椴话l(fā)信號的狀態(tài)。即:如果多個線程在等待向事件發(fā)信號,只有一個線程結束其等待狀態(tài),它不是等待事件最長的線程,而是優(yōu)先級最高的線程。
//計算數(shù)據(jù)的類,使用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();//發(fā)出完成信號
}
}
//外部調(diào)用的示例:
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;//目的是使后面要執(zhí)行的Task不必等待執(zhí)行完后才釋放i,讓for繼續(xù)
mEvents[i] = new ManualResetEventSlim(false);//對應任務的事件對象發(fā)出信號
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);//等待任何一個發(fā)出信號,并返回發(fā)出信號的索引
if (index == WaitHandle.WaitTimeout)
{
Console.WriteLine("Timeout!!");
}
else
{
mEvents[index].Reset();//重新設置為無信號狀態(tài)
Console.WriteLine("finished task for {0}, result: {1}", index, calcs[index].Result);
}
}
}
CountdownEvent類適用于:需要把一個工作任務分配到多個任務中,然后在各個任務結束后合并結果(不需要為每個任務單獨創(chuàng)建事件對象)。每個任務不需要同步。CountdownEvent類為所有設置了事件的任務定義了一個初始數(shù)字,達到該計數(shù)后,就發(fā)出信號。
//修改計算類
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();//發(fā)出完成信號
}
}
//修改方法調(diào)用
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;//目的是使后面要執(zhí)行的Task不必等待執(zhí)行后才釋放i,讓for可以繼續(xù)
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類適用于:工作有多個任務分支,并且在所有任務執(zhí)行完后需要合并的工作情況。與CountdownEvent不同于,該類用于需要同步的參與者。在激活一個任務后,可以動態(tài)的添加其他參與者。在主參與者繼續(xù)之前,可以等待所有其他參與者完成工作。
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));//啟動計算任務:可以分開寫,以執(zhí)行多個不同的任務。
}
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);//統(tǒng)計過的字符串數(shù)量
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]++;//對應字符的數(shù)量+1;
}
Console.WriteLine("Calculation completed from task {0}. {1} times a, {2} times z", Task.CurrentId, charCount[0], charCount[25]);//告知計算完成
barrier.RemoveParticipant();//告知,減少一個參數(shù)者
Console.WriteLine("Task {0} removed from barrier, remaining participants {1}", Task.CurrentId, barrier.ParticipantsRemaining);
return charCount;//返回統(tǒng)計的結果
}
//用于填充一個字符串鏈表
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));//創(chuàng)建一個6個字符的隨機字符串
}
return sb.ToString();
}
6、ReaderWriterLockSlim 類
該類是使鎖定機制允許鎖定多個讀取器(而不是一個寫入器)訪問某個資源:如果沒有寫入器鎖定資源,那么允許多個讀取器訪問資源,但只能有一個寫入器鎖定該資源。
由它的屬性可以讀取是否處于堵塞或不堵塞的鎖定,如EnterReadLock()和TryEnterReadLock()方法。也可以獲得其是否處于寫入鎖定或非鎖定狀態(tài),如EnterWriteLock()和TryEnterWriteLock()方法。如果任務需要先讀取資源,之后寫入資源,可以使用EnterUpgradeableReadLock()或TryEnterUpgradeableReadLock()方法獲取可升級的讀取鎖定。該鎖定可以獲取寫入鎖定,而不需要釋放讀取鎖定。
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# 進程之間的線程同步的詳細內(nèi)容,更多關于c# 線程同步的資料請關注腳本之家其它相關文章!
相關文章
如何使用C#將Tensorflow訓練的.pb文件用在生產(chǎn)環(huán)境詳解
這篇文章主要給大家介紹了關于如何使用C#將Tensorflow訓練的.pb文件用在生產(chǎn)環(huán)境的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧2018-11-11
Unity3D運行報DllNotFoundException錯誤的解決方案
這篇文章主要介紹了Unity3D運行報DllNotFoundException錯誤的解決方案,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04
C#使用HttpPost請求調(diào)用WebService的方法
這篇文章主要為大家詳細介紹了C#使用HttpPost請求調(diào)用WebService的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08

