【渐进】设计一个模拟并行的线程同步组件
您是否遇到下述几个场景:
- 一个算法中有些步骤希望可以并行执行以提高计算效率。
- 在一个工作流中,其中一个活动执行完毕后进入多个并行的分支活动,所有分支活动处理完毕后再返回主流程继续下一个活动。
本文就将针对上述场景的需求设计一个组件来支持您的设计。
从以上的几个场景我们可以分析出其需求:
“主线程执行中途,需要等待几个子线程的处理结束后,继续主线程的执行。”
于是,我们将依据此设计一个能满足此特性的组件,也许您已经想到可以简单的利用事件等通知机制来让子线程执行完毕后发出通知然后进行下一步操作:
上图描述了利用事件,消息等机制后形成的执行模型。
再来看一下这张图:
您是否希望让主/子线程编程能在一个方法区域内保持线性的语法书写,而不是通过Callback等方式打乱原本的书写顺序和连贯性?
瞻仰并行
dotnet3.5开始提供了并行计算的支持,PLinq就是Linq的并行版本,在System.Theading空间(是一个单独的并行库System.Theading.dll)下,4.0则极大的增强了并行编程的支持。PLinq给我们提供了如下的启用并行的语法对集合进行遍历:
- new List<object>().AsParallel().ForAll(o => { });
测试一下:
- var p = new List<Action>(){() => { Thread.Sleep(1000); Console.WriteLine("子任务1完成"); }
- , () => { Thread.Sleep(1000); Console.WriteLine("子任务2完成"); }
- , () => { Thread.Sleep(1000); Console.WriteLine("子任务3完成"); }
- , () => { Thread.Sleep(1000); Console.WriteLine("子任务4完成"); }
- , () => { Thread.Sleep(1000); Console.WriteLine("子任务5完成"); }
- , () => { Thread.Sleep(1000); Console.WriteLine("子任务6完成"); }
- , () => { Thread.Sleep(1000); Console.WriteLine("子任务7完成"); }}.AsParallel();
- DateTime begin = DateTime.Now;
- p.ForAll(O => O());
- Console.WriteLine("?时" + (DateTime.Now - begin).TotalMilliseconds);
- begin = DateTime.Now;
- p.ForAll(O => O());
- Console.WriteLine("?时" + (DateTime.Now - begin).TotalMilliseconds);
上述代码的执行效率,根据CPU的核心数,能提高几乎相同的倍数。事实上3.5对并行的支持只算是一个初步的预览。设计使用不当,未必能带来多少的提升。其实现原理同样是利用多线程,并且System.Theading中提供专用的线程池来优化线程的利用。
回到我们的主题上来,上文所确立的需求有点类似并行处理,这是执行行为上类似,不过设计目标还是有所不同的,我们旨在设计线程同步组件,来支持主子线程的同步和语法上的流畅,而并行计算的目的是尽可能的利用CPU的计算能力。
既然是要进行线程同步,就要有相应的同步机制,这里采用了WaitHandle(可参考http://msdn.microsoft.com/zh-cn/library/system.threading.waithandle(VS.80).aspx)来完成这个设计。 关于AutoResetEvent和ManualResetEvent,二者都派生自WaitHandle,类似信号灯的作用,二者区别在于前者会自动唤醒一个线程,后者可编程控制唤醒多个线程。
文章后面将附上代码下载,设计仍有可改进的空间,如线程的利用等。
调用语法:
- //create
- var paralle = Paralle.Create();
- //add task
- paralle.Add(() => { Thread.Sleep(1000); });
- //start
- paralle.Begin();
- //return main thread
- //...
测试代码:
- Console.WriteLine("并?模拟组件测??");
- Console.WriteLine("主任务开始。");
- Thread.Sleep(5000);
- Console.WriteLine("主任务暂停。");
- Console.WriteLine("");
- //第一次使用
- DateTime begin = DateTime.Now;
- var p = Paralle.Create();
- p.Add(() => { Thread.Sleep(1000); Console.WriteLine("子任务1完成"); }
- , () => { Thread.Sleep(1000); Console.WriteLine("子任务2完成"); }
- , () => { Thread.Sleep(1000); Console.WriteLine("子任务3完成"); }
- , () => { Thread.Sleep(1000); Console.WriteLine("子任务4完成"); });
- Console.WriteLine("初始化?时(ms)?" + (DateTime.Now - begin).TotalMilliseconds);
- begin = DateTime.Now;
- p.Begin();
- Console.WriteLine("执??时(ms)?" + (DateTime.Now - begin).TotalMilliseconds);
- Console.WriteLine("");
- Console.WriteLine("主任务继续。");
- Thread.Sleep(2000);
- Console.WriteLine("主任务暂停。");
- Console.WriteLine("");
- //第二次使用
- p.Clear();
- begin = DateTime.Now;
- p.Add(() => { Thread.Sleep(1000); Console.WriteLine("子任务5完成"); }
- , () => { Thread.Sleep(1000); Console.WriteLine("子任务6完成"); }
- , () => { Thread.Sleep(1000); Console.WriteLine("子任务7完成"); }
- , () => { Thread.Sleep(1000); Console.WriteLine("子任务8完成"); });
- Console.WriteLine("初始化?时(ms)?" + (DateTime.Now - begin).TotalMilliseconds);
- begin = DateTime.Now;
- p.Begin();
- Console.WriteLine("执??时(ms)?" + (DateTime.Now - begin).TotalMilliseconds);
- Console.WriteLine("");
- Console.WriteLine("主任务结束。");
结果:
如上图所示,主任务暂停后,等待子任务执行完毕就继续执行紧接着的代码段(即主任务)。
代码下载地址:/Files/wsky/ParalleV1.0.rar