Barrier,中文被译为屏障。在C#中,可以用来实现多任务在多阶段中协同工作。通俗来讲,就是多个线程在执行到某个被共同指定步骤(即Barrier.SignalAndWait())的时候,就像遇到了屏障一样,必须等待其他还未执行到该步骤的线程。如果每个线程都执行到了该步骤,则大家又继续执行各自的逻辑。
Barrier对象可防止并行操作中的各个任务在所有任务到达屏障前继续执行。 如果并行操作分阶段执行,且每个阶段需要在任务之间进行同步,此对象就很有用。
在实例化Barrier对象的时候,还可以指定一个Action委托,该委托会在所有任务到达屏障后立即执行,接着各个任务又继续执行它们各自的逻辑。该Action委托只与Barrier对象关联,无论有多少个任务,只要它们都达到屏障后,该Action委托就会执行一次。
Barrier.SignalAndWait()该方法会通知Barrier对象,一个线程已经到达了屏障,需要等待其他还未到达屏障的线程。
1、普通使用方式
代码如下所示:
//指定3个并行任务,当三个并行任务到达屏障后,就执行预先定义的Action委托 //如果有第4个任务执行了SignalAndWait()方法,就会报错,因为当前Barrier对象只指定了3个可以并行的任务数量 static Barrier barrier=new Barrier(3, it => { Console.WriteLine($"Barrier委托 开始执行"); Console.WriteLine($"Barrier委托 获取Barrier.CurrentPhaseNumber={barrier.CurrentPhaseNumber}"); Thread.Sleep(2000); Console.WriteLine($"Barrier委托 执行完毕"); }); static void WorkOnBarrierOne(string threadName) { Console.WriteLine($"{threadName} 开始执行"); Console.WriteLine($"{threadName} 获取Barrier.CurrentPhaseNumber={barrier.CurrentPhaseNumber}"); Console.WriteLine($"{threadName} 到达屏障"); barrier.SignalAndWait(); Console.WriteLine($"{threadName} 再次获取Barrier.CurrentPhaseNumber={barrier.CurrentPhaseNumber}"); Console.WriteLine($"{threadName} 执行完毕"); } static void WorkOnBarrierTwo(string threadName) { Console.WriteLine($"{threadName} 开始执行"); Console.WriteLine($"{threadName} 获取Barrier.CurrentPhaseNumber={barrier.CurrentPhaseNumber}"); Thread.Sleep(1000); Console.WriteLine($"{threadName} 到达屏障"); barrier.SignalAndWait(); Console.WriteLine($"{threadName} 再次获取Barrier.CurrentPhaseNumber={barrier.CurrentPhaseNumber}"); Console.WriteLine($"{threadName} 执行完毕"); }
Main方法中的代码:
Thread t1 = new Thread(() => WorkOnBarrierOne("线程1")); Thread t2 = new Thread(() => WorkOnBarrierTwo("线程2")); t1.Start(); t2.Start(); Console.WriteLine($"主线程 到达屏障"); barrier.SignalAndWait(); Console.WriteLine($"主线程 获取Barrier.CurrentPhaseNumber={barrier.CurrentPhaseNumber}"); Console.WriteLine($"主线程 执行完毕");
结果:
从结果中可以看出,只有指定的所有线程到达屏障(Barrier.SignalAndWait()方法处)之后,在Barrier对象中预先定义的Action委托就会执行,Action委托执行完毕之后,Barrier的CurrentPhaseNumber就会自增1,即表示当前阶段数量自增1。
Barrier可以执行很多阶段,可以将Barrier.SignalAndWait()方法放在一个循环代码中。
2、超时等待
可以在Barrier.SignalAndWait(int millisecondsTimeout)中使用超时等待结果。如果等待超时,则返回false,未超时,则返回true。
在超时等待之后,需要移除一个参与者,否则其他的线程就会一直阻塞等待。
因为超时等待后,就像一个人先跑了,而其他人到达屏障后并不知道有人先跑掉,他们会一直等待,因为只有人数在聚齐的情况下,他们才会又各自散去。
而此时,将先跑的人数减少,则剩下的就有机会聚齐。
Main代码如下所示:
Thread t1 = new Thread(() => WorkOnBarrierOne("线程1")); Thread t2 = new Thread(() => WorkOnBarrierTwo("线程2")); t1.Start(); t2.Start(); Console.WriteLine($"主线程 到达屏障"); //等待超时后,Barrier.SignalAndWait()会返回false,没有超时,会返回true if (!barrier.SignalAndWait(1000)) { Console.WriteLine($"主线程 到达屏障后等待超时"); //等待超时后,需要移除一个参与者,否则其他的线程就会一直等待 //因为超时等待后,就像一个人先跑了,而其他人到达屏障后并不知道有人先跑掉,他们会一直等待,因为只有人数在聚齐的情况下,他们才会又各自散去 //而此时,将先跑的人数减少,则剩下的就有机会聚齐 barrier.RemoveParticipant(); } Console.WriteLine($"主线程 获取Barrier.CurrentPhaseNumber={barrier.CurrentPhaseNumber}"); Console.WriteLine($"主线程 执行完毕");
超时结果如下所示:
参考文献:https://docs.microsoft.com/zh-cn/dotnet/standard/threading/barrier