有這樣一個題目:四個線程t1,t2,t3,t4,向4個文件中寫入數據,要求:t1只能寫入“1”,t2只能寫入“2”,t3只能寫入“3”,t4只能寫入“4”,對4個文件A,B,C,D寫入如下內容:
簡單分析一下,對於A文件,t1寫入“1”後,我們希望通知t2來寫“2”,並且t1前往D文件等著去寫“1”,以此類推。
1. 通過等待句柄實現
顯然可以用等待句柄來實現,通知t2來寫“2”相當於在一個等待句柄上調用 Set 方法,等待在D文件上寫“1”相當於在另一等待句柄上調用了 WaitOne 方法,下邊是利用4個 AutoResetEvent 來實現它:
class Program
{
private static List<StreamWriter> _sws;
private static List<AutoResetEvent> _whs;
private static void Main(string[] args)
{
var fileNames = new List<string> {"A", "B", "C", "D"};
// 創建或清空文件
fileNames.ForEach(name =>
{
if (!File.Exists(name))
File.Create(name);
else
{
using (var sw = new StreamWriter(name))
sw.Write("");
}
});
_sws = fileNames.Select(File.AppendText).ToList();
// 為每個文件寫入建立一個等待句柄
_whs = fileNames.Select(name => new AutoResetEvent(false)).ToList();
// 創建並啟4個線程執行任務
var threads = new List<Thread> {
new Thread(() => Work(1)), new Thread(() => Work(2)), new Thread(() => Work(3)), new Thread(() => Work(4))
};
threads.ForEach(t => t.Start());
// 等待線程結束並關閉 StreamWrite
threads.ForEach(t => t.Join());
Console.WriteLine("任務完成!");
_sws.ForEach(sw => sw.Close());
}
static void Work(int threadIndex)
{
var next = threadIndex - 1;
// 為讓程序能結束,就打印100次
for (int i = 0; i < 100; i++)
{
var wh = _whs[next];
var sw = _sws[next];
lock (sw)
{
sw.Write(threadIndex);
}
next = (next - 1) < 0 ? 3 : next - 1;
WaitHandle.SignalAndWait(wh, _whs[next]); //在wh上發信號,並在下一個等待句柄上等待執行
}
}
}
上述例子中我們創建了4個線程來分別打印1,2,3,4,並且為每個文件的寫入創建了4個等待句柄來進行信號通信。最後主線程在等待所有線程結束後,關閉文件流。為讓程序能正常結束,在 Work 方法中就只循環寫100次。
以t1(列表中第一個線程)為例,在A文件中打印1後,調用 WaitHandle.SignalAndWait(wh,wh[next]),即在wh上發信號通知可以接著寫入了,並在下一個等待句柄上等待寫入信號。
關於 SinalAndWait 的可以參見 Thread in C# 或者對應的 中文翻譯 。
2. 通過 Barrier 類實現
除了通過等待句柄可以實現題目要求外,同樣可以通過 Wait 和 Pulse 來實現。如果是FrameWork 4.0或更高的版本,可以通過 Barrier 類(它是建立在 Wait / Pulse 和自旋鎖基礎上的)更簡單的實現這個題目。
class Program
{
private static Barrier _barrier;
private static void Main(string[] args)
{
var fileNames = new List<string> { "A", "B", "C", "D" };
// 創建或清空文件
fileNames.ForEach(name =>
{
if (!File.Exists(name))
File.Create(name);
else
{
using (var sw = new StreamWriter(name))
sw.Write("");
}
});
// 在_barrier上調用SignalAndWait的線程會被阻塞直到這個方法被調用4次
_barrier = new Barrier(4);
_sws = fileNames.Select(File.AppendText).ToList();
// 創建並啟4個線程執行任務
var threads = new List<Thread> {
new Thread(() => Work(1)), new Thread(() => Work(2)), new Thread(() => Work(3)), new Thread(() => Work(4))
};
threads.ForEach(t => t.Start());
// 等待線程結束並關閉 StreamWrite
threads.ForEach(t => t.Join());
Console.WriteLine("任務完成!");
_sws.ForEach(sw => sw.Close());
}
static void Work(int threadIndex)
{
var next = threadIndex - 1;
for (int i = 0; i < 100; i++)
{
var sw = _sws[next];
lock (sw)
{
sw.Write(threadIndex);
}
_barrier.SignalAndWait();
next = (next - 1) < 0 ? 3 : next - 1;
}
}
}
使用了一個 Barrier 類來替代4個等待句柄,線程調用 SignalAndWait 後會阻塞,直到這個方法被調用4次。在這個例子中意味著4個線程總是在同步進行著打印,下圖可以很好的解釋 Barrier 類:

關於 Barrier 類,可以參見 Thread in C# 或者對應的 中文翻譯 。