GPars: 体会Dataflow

GPars提供众多的并行抽象,Dataflow模型就是其中之一,如果某个处理所需读取的数据内容都是由其他任务并行提供的,Dataflow可以保证在该处理运行之前,所有所需数据均就绪。但Dataflow的强大和灵活并不止这些,Václav Pech将其使用Dataflow过程中的体会和感悟与大家进行了分享,我们可以共同学习和借鉴。

Dataflow的基本概念和用法,在GPars(11):Dataflow一文中已做了简单介绍,这里不再赘述。

通过运用dataflow变量(DataFlowVariable、Dataflows和DataFlowStream),dataflow的并行模型自然避免了竞争条件(race-conditions),包括活锁(live locks)。至于死锁(deadlocks),虽无法避免,其危害也降低了许多。Dataflow让死锁成为可确定的。即,在任何时间任何系统上,同样的数据输入可再现死锁。简而言之,如果测试时没有死锁,就可放心发布产品了。想想以前写过的Java基于线程的应用,你有多大把握说不会死锁呢?

那么为什么dataflow可以让死锁成为确定的呢?为了说明这一点,我们需引入一点理论:dataflow变量显然在并行任务上引入了“偏序(partial ordering)”。看个例子:

//task 1
task {
    do_A
    read_dataflow
    do_B
}

//task 2
task {
    do_C
    write_dataflow
    do_D
}

task_2要写dataflow变量,而task_1要读取该变量,读写dataflow变量的操作把任务分成了A、B、C、D四个部分,这些部分的执行是部分有序的。我们以“<”来表示顺序,即x<y表示x必须在y之前运行。很显然A<B,而C<D,根据dataflow变量的位置及对其所进行的读写操作,我们可以推断出C<B。借助dataflow模型的约束,我们知道B只会在C之后运行,因此如果C要为B的运行准备数据,我们跟不无需额外的手段来保证B执行之前C已将数据准备完毕。如果画出图来,这一关系更加一目了然。

借助图,我们可以轻易发现死锁的存在。如下例:

//task 1
task {
    read_dataflow_2
    do_A
    do_B
    write_dataflow_1
}

//task 2
task {
    do_C
    read_dataflow_1
    do_D
    write_dataflow_2
}

从图中可以看出,A、B、D形成了环,不可能执行下去。

不同的并行模型适用于不同的需求。对于这一点Václav Pech给出了自己的看法:

对于要把整体目标分解为若干并行任务的需求,parallel collections和fork/join分别适用于集合数据和分层数据;对于大型算法抽象,用actors或CSP这种利用信道通信的模型更适用;当然也有很多需求是适合dataflow并发模型的。我个人觉得对于手工划分的并行任务dataflow就很合适。

By songwei - Posted on 20 七月 2010