[原创] 具有依赖关系的并行操作执行

今天看到看到一篇MSDN文章《Parallelizing Operations With Dependencies,作者是微软Parallel Computing Platform团队的一个开发经理。文中提供出一种用于并行执行一组具有依赖关系的操作的解决方案,这不由得想起我在一年之前写的一个具有相同的功能的组件。于是翻箱倒柜找了出来,进行了一些加工,与大家分享一下。

一、问题分析

我们知道,较之串行化的操作,并行计算将多个任务同时执行,从而充分利用了资源,提高了应用的整体性能。对于多个互不相干的操作,我们可以直接按照异步的方式执行就可以。但是,我们遇到的很多情况下是,部分操作之间具有相互依赖的关系,一个操作需要在其他依赖的操作执行完成后方可执行。 以下图为例,每一个圆圈代表要执行的操作,操作之间的肩头代表它们之间的依赖关系。

clip_image002

我们需要一个组件,帮助我们完成这样的工作:将相应的操作和依赖关系直接添加到一个容器中,我们的组件能够自动分析操作之间的依赖关系,在执行的时候根据依赖编排执行顺序。

二、采用并行操作执行器

使用我所提供的这样一个并行操作执行器(ParallelExecutor),可以帮我们解决这个问题。首先对操作本身进行抽象,用以下三个属性来描述一个并行计算场景中的操作:

  • Operation ID: 操作的唯一标识,字符类型

  • Action:操作具体执行的功能,使用Action代理表示

  • Depedencies:依赖操作列表

在使用ParallelExecutor对操作进行并行执行之前,我们需要通过ParallelExecutor的两个AddOperation方法添加需要执行的操作。AddOperation定义如下。其中dependencies代表以来操作ID数组,返回值为当前创建的操作ID

   1: public class ParallelExecutor
<!--CRLF-->
   2: {
<!--CRLF-->
   3:  
<!--CRLF-->
   4:     public string AddOperation(string id, Action action)
<!--CRLF-->
   5:     {
<!--CRLF-->
   6:         //省略实现
<!--CRLF-->
   7:     }
<!--CRLF-->
   8:  
<!--CRLF-->
   9:     public string AddOperation(string id, Action action, string[] dependencies)
<!--CRLF-->
  10:     {
<!--CRLF-->
  11:         //省略实现
<!--CRLF-->
  12:     }
<!--CRLF-->
  13: }
<!--CRLF-->
  14: 
<!--CRLF-->

对于上图中的操作的依赖结构,我们通过下面的代码将所有的操作添加到创建的ParallelExecutor之中并执行。在这里的具体实现的操作仅仅是打印出操作的ID,以便我们清楚地知道操作执行的先后顺序是否满足依赖关系:

   1: static void Main(string[] args)
<!--CRLF-->
   2: {
<!--CRLF-->
   3:     Action<string> action = id=> {Console.WriteLine(id);}; 
<!--CRLF-->
   4:  
<!--CRLF-->
   5:     var executor = new ParallelExecutor();
<!--CRLF-->
   6:     var a1 = executor.AddOperation("A1", () => action("A1"));
<!--CRLF-->
   7:     var a2 = executor.AddOperation("A2", () => action("A2"));
<!--CRLF-->
   8:     var a3 = executor.AddOperation("A3", () => action("A3")); 
<!--CRLF-->
   9:  
<!--CRLF-->
  10:     var b1 = executor.AddOperation("B1", () => action("B1"), new string[] { a1, a2 });
<!--CRLF-->
  11:     var b2 = executor.AddOperation("B2", () => action("B2"), new string[] { a3 }); 
<!--CRLF-->
  12:  
<!--CRLF-->
  13:     var c1 = executor.AddOperation("C1", () => action("C1"), new string[] { b1,b2 });
<!--CRLF-->
  14:     var c2 = executor.AddOperation("C2", () => action("C2")); 
<!--CRLF-->
  15:  
<!--CRLF-->
  16:     executor.Execute();
<!--CRLF-->
  17: Console.Read();
<!--CRLF-->
  18: }
<!--CRLF-->
  19: 
<!--CRLF-->

由于是操作的并行执行,线程调度的不确定性使每次输出的结果各有不同。但是无论如何,需要满足上图中展现的依赖关系。下面是其中一种执行结果,可以看出这是合理的执行顺序。

   1: A3
<!--CRLF-->
   2: B2
<!--CRLF-->
   3: A1
<!--CRLF-->
   4: A2
<!--CRLF-->
   5: C2
<!--CRLF-->
   6: B1
<!--CRLF-->
   7: C1
<!--CRLF-->

三、操作是如何被执行的

实现这样的并行计算有很多种解决方案。不同的解决方案大都体现在对于单一的操作该如何执行上。在我们提供这个解决方案中,我按照这样的方案来执行任意一个操作:

直接执行无依赖的操作

如果需要执行的操作并不依赖于任何一个操作(比如C2),那么我们直接运行就好了,这没有什么好说的。

先执行依赖操作,通过注册事件的方式执行被依赖的操作

如果一个操作依赖于一组操作,在执行之前注册依赖操作的结束事件实现,被依赖操作的执行发生在某个一个依赖操作的Completed事件触发后。具体来讲,上图中C1具有两个以来操作B1B2,在初始化时,C1上会有一个用于计算尚未执行的依赖操作的个数,并注册B1B2得操作结束事件上面。当B1B2执行结束后,会触发事件。每次事件触发,C1上的计数器将会减1,如果计数器为0,则表明所有的依赖操作执行结束,则执行C1相应的操作。

四、具体实现

现在我们来看看详细设计和具体实现。首先通过下面的类图看看涉及到的所有类型。其中Operation类型是最为重要的一个类型,它代表一个具体的操作。

clip_image004

操作的属性

一个操作具有如下属性:

  • IDString类型,操作的唯一标识

  • ActionAction类型,操作具体是实现的功能

  • DependenciesOperation数组,依赖的操作

  • StatusOperation枚举,操作当前的状态

  • ExecutionContextExecutionContext类型,用于传递线程执行的上下文


   1: public class Operation
<!--CRLF-->
   2: {    
<!--CRLF-->
   3:     //其他成员
<!--CRLF-->
   4:     public string ID
<!--CRLF-->
   5:     { get; private set; } 
<!--CRLF-->
   6:  
<!--CRLF-->
   7:     public Action Action
<!--CRLF-->
   8:     { get; private set; } 
<!--CRLF-->
   9:  
<!--CRLF-->
  10:     public Operation[] Dependencies
<!--CRLF-->
  11:     { get; private set; } 
<!--CRLF-->
  12:  
<!--CRLF-->
  13:     public OperationStatus Status
<!--CRLF-->
  14:     { get; private set; } 
<!--CRLF-->
  15:  
<!--CRLF-->
  16:     public ExecutionContext ExecutionContext
<!--CRLF-->
  17:     { get; private set; } 
<!--CRLF-->
  18:  
<!--CRLF-->
  19:     public Operation(string id, Action action)
<!--CRLF-->
  20:     {
<!--CRLF-->
  21:         if (string.IsNullOrEmpty(id))
<!--CRLF-->
  22:         {
<!--CRLF-->
  23:             throw new ArgumentNullException("id");
<!--CRLF-->
  24:         } 
<!--CRLF-->
  25:  
<!--CRLF-->
  26:         if (null == action)
<!--CRLF-->
  27:         {
<!--CRLF-->
  28:             throw new ArgumentNullException("action");
<!--CRLF-->
  29:         }
<!--CRLF-->
  30:         this.Status = OperationStatus.Created;
<!--CRLF-->
  31:         this.ID = id;
<!--CRLF-->
  32:         this.Action = action;
<!--CRLF-->
  33:         this.Dependencies = new Operation[0];
<!--CRLF-->
  34:     } 
<!--CRLF-->
  35:  
<!--CRLF-->
  36:     public Operation(string id, Action action, Operation[] dependencies)
<!--CRLF-->
  37:         : this(id, action)
<!--CRLF-->
  38:     {
<!--CRLF-->
  39:         if (null == dependencies)
<!--CRLF-->
  40:         {
<!--CRLF-->
  41:             throw new ArgumentNullException("dependencies");
<!--CRLF-->
  42:         } 
<!--CRLF-->
  43:  
<!--CRLF-->
  44:         this.Dependencies = dependencies;
<!--CRLF-->
  45:     }     
<!--CRLF-->
  46: }
<!--CRLF-->

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值