[原创] 具有依赖关系的并行操作执行
|
今天看到看到一篇MSDN文章《Parallelizing Operations With Dependencies》,作者是微软Parallel Computing Platform团队的一个开发经理。文中提供出一种用于并行执行一组具有依赖关系的操作的解决方案,这不由得想起我在一年之前写的一个具有相同的功能的组件。于是翻箱倒柜找了出来,进行了一些加工,与大家分享一下。
一、问题分析
我们知道,较之串行化的操作,并行计算将多个任务同时执行,从而充分利用了资源,提高了应用的整体性能。对于多个互不相干的操作,我们可以直接按照异步的方式执行就可以。但是,我们遇到的很多情况下是,部分操作之间具有相互依赖的关系,一个操作需要在其他依赖的操作执行完成后方可执行。 以下图为例,每一个圆圈代表要执行的操作,操作之间的肩头代表它们之间的依赖关系。
我们需要一个组件,帮助我们完成这样的工作:将相应的操作和依赖关系直接添加到一个容器中,我们的组件能够自动分析操作之间的依赖关系,在执行的时候根据依赖编排执行顺序。
二、采用并行操作执行器
使用我所提供的这样一个并行操作执行器(ParallelExecutor),可以帮我们解决这个问题。首先对操作本身进行抽象,用以下三个属性来描述一个并行计算场景中的操作:
在使用ParallelExecutor对操作进行并行执行之前,我们需要通过ParallelExecutor的两个AddOperation方法添加需要执行的操作。AddOperation定义如下。其中dependencies代表以来操作ID数组,返回值为当前创建的操作ID。
1: public class ParallelExecutor 2: {
3: 4: public string AddOperation(string id,Action action) 5: {
6: //省略实现 7: } 8: 9: public string AddOperation(string id,Action action,string[] dependencies) 10: {
11: //省略实现 12: } 13: } 14: 对于上图中的操作的依赖结构,我们通过下面的代码将所有的操作添加到创建的ParallelExecutor之中并执行。在这里的具体实现的操作仅仅是打印出操作的ID,以便我们清楚地知道操作执行的先后顺序是否满足依赖关系:
1: static void Main(string[] args)
3: Action<string> action = id=> {Console.WriteLine(id);};
4: 5: var executor = new ParallelExecutor(); 6: var a1 = executor.AddOperation("A1",() => action("A1"));
7: var a2 = executor.AddOperation("A2",() => action("A2"));
8: var a3 = executor.AddOperation("A3",() => action("A3"));
9: 10: var b1 = executor.AddOperation("B1",() => action("B1"),new string[] { a1,a2 });
11: var b2 = executor.AddOperation("B2",() => action("B2"),new string[] { a3 });
12: 13: var c1 = executor.AddOperation("C1",() => action("C1"),new string[] { b1,b2 });
14: var c2 = executor.AddOperation("C2",() => action("C2"));
15: 16: executor.Execute(); 17: Console.Read(); 18: } 19: 由于是操作的并行执行,线程调度的不确定性使每次输出的结果各有不同。但是无论如何,需要满足上图中展现的依赖关系。下面是其中一种执行结果,可以看出这是合理的执行顺序。
1: A3
2: B2 3: A1 4: A2 5: C2 6: B1 7: C1 三、操作是如何被执行的
实现这样的并行计算有很多种解决方案。不同的解决方案大都体现在对于单一的操作该如何执行上。在我们提供这个解决方案中,我按照这样的方案来执行任意一个操作:
直接执行无依赖的操作
如果需要执行的操作并不依赖于任何一个操作(比如C2),那么我们直接运行就好了,这没有什么好说的。
先执行依赖操作,通过注册事件的方式执行被依赖的操作
如果一个操作依赖于一组操作,在执行之前注册依赖操作的结束事件实现,被依赖操作的执行发生在某个一个依赖操作的Completed事件触发后。具体来讲,上图中C1具有两个以来操作B1和B2,在初始化时,C1上会有一个用于计算尚未执行的依赖操作的个数,并注册B1和B2得操作结束事件上面。当B1和B2执行结束后,会触发该事件。每次事件触发,C1上的计数器将会减1,如果计数器为0,则表明所有的依赖操作执行结束,则执行C1相应的操作。
四、具体实现
现在我们来看看详细设计和具体实现。首先通过下面的类图看看涉及到的所有类型。其中Operation类型是最为重要的一个类型,它代表一个具体的操作。
操作的属性
一个操作具有如下属性:
1: public class Operation
2: {
3: //其他成员 4: public string ID 5: { get; private set; }
6: 7: public Action Action 8: { get; private set; }
10: public Operation[] Dependencies 11: { get; private set; }
13: public OperationStatus Status 14: { get; private set; }
16: public ExecutionContext ExecutionContext 17: { get; private set; }
18: 19: public Operation(string id,Action action) 20: {
21: if (string.IsNullOrEmpty(id)) 22: {
23: throw new ArgumentNullException("id");
24: } 25: 26: if (null == action) 27: {
28: throw new ArgumentNullException("action");
29: } 30: this.Status = OperationStatus.Created; 31: this.ID = id; 32: this.Action = action; 33: this.Dependencies = new Operation[0]; 34: } 35: 36: public Operation(string id,Operation[] dependencies) 37: : this(id,action) 38: {
39: if (null == dependencies) 40: {
41: throw new ArgumentNullException("dependencies");
42: } 43: 44: this.Dependencies = dependencies; 45: } 46: } 47: (编辑:安卓应用网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
