Skip to content

开发日志 0.0.8

十大才子之首 edited this page Jun 14, 2022 · 4 revisions

在0.0.8版本,我更希望crow在多线程的操作模块更加高效,这包括以下几个方面

  1. 开放串行化执行的结构,通过一个数据结构来保存串行化执行中每一个节点所产生的结果
  2. 让并行化执行的相对可控
  3. 并行化执行的节点交互

新功能1

思路说明

crow的串行化整体来看是一个链型结构,假设一个串行化任务中存在A,B,C,D,E等任务则其执行如下图所示:

graph  LR
A((A)) --> B((B)) --> C((C)) --> D((D)) --> E((E))

在这个执行的链表中,B会获得A的结果,C会获得B的结果。。。E会获得D的结果。这样一来后面的任务想要开始执行就必须等待前面的任务执行完成,在这中情况下为了减少线程调度的消耗,最好将这些任务放在一个线程中执行。在0.0.7以及以前的版本中,串行化任务的实现依赖于CompletableFuture,在CompletableFuture中每个实例的结果在其执行完成后可以随时去获取,Multi同样有这个特性。

这种情况下就可以使用一个队列来保存Multi进而保存其结果

为什么要使用队列?

任务转配的过程很像是一个队列,最先开始执行的A任务的结果最先获得,这就是“先进先出”的模式,故使用队列存储。

使用ArrayBlockingQueue,在新的任务加入前必须保证两个任务之间的关联性,即下一个任务如果需要的话一定需要的是上一个任务的结果,这就表示同一时间只能有一个任务添加到串行化过程中,BlockingQueue正好符合这一需求,串行化的处理节点通常不会太多这种情况下使用ArrayBlockingQueue再适合不过。

说明

既然使用到了ArrayBlockingQueue就必须要为其扩容,以及扩容后的节点迁移问题。

当然最难的还是异常节点,如果串行化结构中有一个节点存在异常那么自这个节点之后的串行化过程都会抛出这个异常,如果上图中的B节点为异常节点,那么串行化过程到达2时就会抛出异常或者进入异常处理节点,之后的C,D,E节点根本无法到达,就像最开始说的:在串行化结构中,后面的节点必须在前一个节点执行完成后才会执行。

新功能2

对于任务的执行顺序,是很难控制的,某个线程先调用还是某个线程后被调用,在java代码的层面无法控制,我们可以控制的只有任务的结果的输出顺序。

实现

如果没有指定当前任务的序号,那么应该使用一个全局的量来存储这个序号的当前值,同时保证新增加的任务和已存在的任务的序号不能相同,为了计算方便,并行化执行的序号初始量为0且不能指定为负数。同时维持一个hash表来记录某个序号有没有被使用,每次添加节点时验证并记录。

最终在获取和结果时进行排序。

异常处理任务

异常节点,本来对于并行化执行的任务,并不想为其添加异常处理任务,但是多线程处理任务如果没有特殊的异常处理任务,那么真正需要注意到的异常就会成为一个嵌套异常。所以异常处理节点是必须的。

在并行化任务中我希望可以这样:仅仅设置一个异常处理,他可以为每一个节点服务。

但是这不得不面对类型冲突的问题,并行化执行中每一个的任务的结果类型都是不同的,但是添加一个任务只能指定一个结果类型,所以这种处理方式是不可取的,只能进行特化的异常处理。

不过在java中Object类是所有的类的父类,可以使用Object来作为异常节点的接入和结果类型,这样可以解决通用性的问题。但我想让这个功能更加灵活,这可以通过之前的hash表来实现:

  • null,序号未使用
  • 0,序号已使用
  • 1,序号已指明异常处理

这样就可以通过序号来解决特定任务的异常处理了

装入时机

装入异常节点的最好时机是在获取结果之前,因为在Multi中,设置的异常节点无法被清除,也无法被取代,这说明后面的异常节点永远无法被触发。

异常处理的覆盖

对于同一个序号先后指定的异常会执行覆盖策略,当然全局设置的除外,全局设置仅会设置没有异常处理节点的节点。

覆盖的逻辑需要序号的支持,这需要知道那个序号对应的任务实例,这样既可以借助hash表也可以使用并行化任务中的任务存储集合,处于便易考虑此处将hash表修改为无重复的集合set,将异常处理节点直接存放到任务集合的实例中。

新功能3

问题引入:

通常来说任务的状态是很不稳定的,一个任务可以拆分为多个任务,相反同样成立,在0.0.7以及以前的版本是不支持节点的任意组合的,这是因为任务的转配和执行是同时进行的,而串行化为了保证结果的顺序则必须保证任务之间存在一个顺序,如果这个顺序被打乱那么索引的顺序也会被打乱,获得的结果准确性就无法得到保证。

这一点在0.0.8版本得到了改善,通过一个int类型的索引值来确定其位置,现在将该字段的叠加线程安全化就可以正常使用了,不过其顺序依然无法得到万全的保障,如下图:

如图这是一个并行任务,A在某一时间点会分裂成B和C两个任务,分裂前后一共是3个任务,但是A是否仍能算是一个任务,A所占用的索引值是否仍能使用,为了平衡这几种情况,应该区别对待:

  1. 第一种情况:A继续使用,当然就算是继续使用的A也可能发生变化,它更像下面这种情况:

在A的基础上直接分离出了B,前提是B是由A产生的,但是从这个节点开始B相对于A来说独立了

  1. 第二种情况:A不再使用,这就代表着A的执行结果对于本次并行执行的结果来说已经失去了意义,如下图:

在生成B和C后A被置为无效,A之前的索引会被清空,B和C同时加入到任务中

解决方案

对于在新功能二中优化过的并行化结构来说较为方便,但是因为任务执行的随机性和序号的不可重复性还有结果类型的不等性等原因,导致任务添加和难以确定其索引值,而且加入新功能三的目的是为了提高任务之间的交互性,而并行执行的准则是打断各个任务之间的关联,使其各个独立运行。

由此看来无论是使用并行执行还是串行执行都无法完美的完成这个任务。

那么引入混合执行过程:MixMulti

结语

关于新功能三会在下个版本完善