翻译自:http://beust.com/weblog/2009/12/13/more-on-multithreaded-topological-sorting-2/
限于翻译水平有限,有兴趣的同学可以直接看原文。翻译上如果有什么不妥之处,还麻烦指正,感谢!
---------------------------------------------------正文的分割线------------------------------------------------------------
在上篇文章中我介绍了在TestNG中新实现的多线程拓扑排序(译注:严格说来,应该是更加动态的拓扑排序,为了支持对待测试方法的并发运行,上一篇文章的译文连接在这里),这篇文章收到了一些有意思的评论,其中有一条来自Rafael Naufal的评论我想拿出来讨论一下:
他提到:
@Priority注解难道不能改进,从而能够表明哪个独立方法应该被首先调度?顺便说一下,判断哪些方法是独立的难道不能放到测试方法的graph对象中么?
这个想法让当前的算法更进一步,即我们不仅需要在测试方法变成独立节点的时候去调度它们,最好还能够根据它们重要的程度去调度。
什么让一个节点比其它的节点更加重要?是它的被依赖程度。一个节点被依赖的越多,那么尽快调度这个节点就更有益,因为这个节点一旦运行结束,就能够让更多的节点变成独立状态。诚然,你还是会受到线程池大小的限制(译注:即最大并发数仍受到线程池大小的限制),但是这就是我们需要的:增加线程池的大小就能够增加并发度,因此也就带来了更好的性能,但是现在公平的(或者称为随机的)调度算法并不能保证性能会增加。
我的第一个反应就是修改我的Executor,但是实际上你能够通过现有的实现来达到目的。Executors的构造方法中接受一个BlockingQueue作为参数,Executor会根据这个queue来处理其中的workers。毫不意外地,现在已经有一个优先队列的实现,叫做PriorityBlockingQueue。
你需要做的就是在创建你自己的Executor时,使用这个带有queue作为参数的构造方法,而不是默认的构造方法,然后还需要保证你传入的workers需要拥有自然排序(译注:其实也不一定需要自然排序,显式的指定一个Comparator也是可行的,这是因为PriorityBlockingQueue需要知道如何确定优先级,而此情此景的优先级,就是worker的权重,如后文)。在这种情况下,worker的权重就是有多少其他的workers依赖它,这是非常容易计算的。
与此相关的,我想仔细看看在我上一篇博文中提到的算法是如何工作的。我描述了一些理论,同时我对该算法进行了一些测试,算法也符合预期,但是我突然想起来我能够很轻松的从细节的角度去考虑这些问题。(译注:因为这些代码本来就是博主写的……,他的意思应该是他没有考虑到读者可能对细节不太熟悉)
首先,我添加了一个toDot方法,用来产生一个表示当前图的Graphviz文件,这很容易实现:
/** * @return a .dot file (GraphViz) version of this graph. */ public String toDot() { String FREE = "[style=filled color=yellow]"; String RUNNING = "[style=filled color=green]"; String FINISHED = "[style=filled color=grey]"; StringBuilder result = new StringBuilder("digraph g {\n"); Set<T> freeNodes = getFreeNodes(); String color; for (T n : m_nodesReady) { color = freeNodes.contains(n) ? FREE : ""; result.append(" " + getName(n) + color + "\n"); } for (T n : m_nodesRunning) { color = freeNodes.contains(n) ? FREE : RUNNING; result.append(" " + getName(n) + color + "\n"); } for (T n : m_nodesFinished) { result.append(" " + getName(n) + FINISHED+ "\n"); } result.append("\n"); for (T k : m_dependingOn.getKeys()) { List<T> nodes = m_dependingOn.get(k); for (T n : nodes) { String dotted = m_nodesFinished.contains(k) ? "style=dotted" : ""; result.append(" " + getName(k) + " -> " + getName(n) + " [dir=back " + dotted +"]\n"); } } result.append("}\n"); return result.toString(); }
然后我修改executor,让它在每个worker终止的时候就绘制一张图,最后,我写了一个shell脚本用来将这些dot文件转换为图像并生成了一个HTML文件。我跑了一个简单的测试,通过该shell脚本处理,最后的得到的结果在这里。
黄色的节点是独立的,绿色代表该节点处于就绪状态(可以在线程池中运行了),灰色代表完成状态,白色则指该节点还没有被处理。点划线的箭头表示已经满足的依赖关系。
正如你看到的,执行的结果与我们描述的算法的工作模式十分接近。同时我也确认了改变线程池的大小能够产生不同的执行方式。