本文主要描述在.netframework中(实验环境.netframework版本为4.6.1)提供两种方式等待多个子线程执行完毕。
- ManualResetEvent
在多线程中,将ManualResetEvent实例作为方法传入,线程执行完毕后可以设置标志位来标识当前线程已经执行完毕。代码如下:
1 List<ManualResetEvent> manualResetEvents = new List<ManualResetEvent>(); 2 /// <summary> 3 /// ManualResetEvent标志多线程是否执行完毕 4 /// </summary> 5 /// <param name="sender"></param> 6 /// <param name="e"></param> 7 private void btn_ManualResetEvent_Click(object sender, EventArgs e) 8 { 9 // SetBtnEnabled(false); 10 Stopwatch watch = new Stopwatch(); 11 watch.Start(); 12 for (int i = 0; i < threadCount; i++) 13 { 14 ManualResetEvent manualReset = new ManualResetEvent(false); 15 manualResetEvents.Add(manualReset); 16 ThreadPool.QueueUserWorkItem(ManualResetEventMethod, manualReset); 17 } 18 //等待所有线程执行完毕 19 WaitHandle.WaitAll(manualResetEvents.ToArray()); 20 //暂停watch,获取多线程执行时间 21 watch.Stop(); 22 long time = watch.ElapsedMilliseconds; 23 lab_time.Text = time.ToString(); 24 25 // SetBtnEnabled(true); 26 27 //释放句柄 28 manualResetEvents.Clear(); 29 } 30 31 private void ManualResetEventMethod(object obj) 32 { 33 Thread.Sleep(1000); 34 ManualResetEvent mre = (ManualResetEvent)obj; 35 mre.Set(); 36 }View Code
注意:
在WaitHandle.WaitAll方法中,等待的句柄不能超过64,所以每次用完后,需要手动调用Clear方法进行释放。
如果等待的线程超过64个,可以参考博客:https://www.cnblogs.com/xiaofengfeng/archive/2012/12/27/2836183.html,在该博客中,通过对ManualResetEvent的封装,能够使等待的句柄超过64(测试环境下一次起1000个线程,没有问题)
- Monitor
在主线程中通过Monitor.Wait(locker)达到阻塞的目的,子线程执行完毕通过 Monitor.Pulse(locker)通知主线程,直到所有子线程执行完成,主线程再继续执行,代码如下:
1 object locker = new object(); 2 int threadCount = 1000; 3 int finshCount = 0; 4 /// <summary> 5 /// Monitor线程之间同步标记多线程执行完毕 6 /// </summary> 7 /// <param name="sender"></param> 8 /// <param name="e"></param> 9 private void btn_Monitor_Click(object sender, EventArgs e) 10 { 11 finshCount = 0; 12 SetBtnEnabled(false); 13 Stopwatch watch = new Stopwatch(); 14 watch.Start(); 15 for (int i = 0; i < threadCount; i++) 16 { 17 Thread trd = new Thread(new ParameterizedThreadStart(MonitorMethod)); 18 trd.Start(i); 19 } 20 lock (locker) 21 { 22 while (finshCount != threadCount) 23 { 24 Monitor.Wait(locker);//等待 25 } 26 } 27 //所有线程执行完毕,获取执行时间 28 watch.Stop(); 29 long time = watch.ElapsedMilliseconds; 30 lab_time.Text = time.ToString(); 31 32 SetBtnEnabled(true); 33 } 34 35 private void MonitorMethod(object obj) 36 { 37 Thread.Sleep(1000); 38 lock (locker) 39 { 40 finshCount++; 41 Monitor.Pulse(locker); //完成,通知等待队列,告知已完,执行下一个。 42 } 43 }View Code
在一次开启10、1000个线程两种环境下,分别测试以上两种方式,ManualResetEvent在多次执行时,前几次耗时会比较大,后续耗时会减少并且稳定下来,接近 Monitor的速度。相对而言,Monitor的效率更高。
如果了解过go语言,会发现通过sync包下的WaitGroup也可以达到同样的目的,代码如下:
package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup var count = 1000 func main() { startTime := time.Now().Unix() wg.Add(count) for i := 0; i < count; i++ { go func() { defer wg.Done() time.Sleep(time.Second) }() } fmt.Println("waiting for all goroutine") wg.Wait() endTime := time.Now().Unix() fmt.Printf("all goroutine is done! time:%v s", (endTime-startTime)) }
相较而言,go语言的协程效率最高