improve wording

This commit is contained in:
Jerry Lee
2017-09-26 21:10:13 +08:00
parent 5352cb8492
commit b050bf11bf

View File

@@ -135,21 +135,21 @@ class Fib extends FJTask {
- 队列以双端队列的形式被维护(注:`deques`通常读作『decks』不仅支持后进先出 —— `LIFO``push``pop`操作,还支持先进先出 —— `FIFO``take`操作。
- 对于一个给定的工作线程来说,任务所产生的子任务将会被放入到工作者自己的双端队列中。
- 工作线程使用后进先出 —— `LIFO`(最早的优先)的顺序,通过弹出任务来处理队列中的任务。
- 当一个工作线程的本地没有任务去运行的时候,它将使用先进先出 —— `FIFO`的规则尝试随机的从别的工作线程中拿(『窃』)一个任务去运行。
- 当一个工作线程的本地没有任务去运行的时候,它将使用先进先出 —— `FIFO`的规则尝试随机的从别的工作线程中拿(『窃』)一个任务去运行。
- 当一个工作线程触及了`join`操作,如果可能的话它将处理其他任务,直到目标任务被告知已经结束(通过`isDone`方法)。所有的任务都会无阻塞的完成。
- 当一个工作线程无法再从其他线程中获取任务和失败处理的时候,它就会退出(通过`yield``sleep`和/或者优先级调整参考第3节并经过一段时间之后再度尝试直到所有的工作线程都被告知他们都处于空闲的状态。在这种情况下他们都会阻塞直到其他的任务再度被上层调用。
使用后进先出 —— `LIFO`用来处理每个工作线程的自己任务,但是使用先进先出 —— `FIFO`规则用于获取别的任务,这是一种被广泛使用的进行递归`Fork/Join`设计的一种调优手段。引用<sup>[5]</sup>讨论了详细讨论了里面的细节。
取任务的线程从队列拥有者相反的方向进行操作会减少线程竞争。同样体现了递归分治算法的大任务优先策略。因此,更早期被取的任务有可能会提供一个更大的单元任务,从而使得取线程能够在将来进行递归分解。
取任务的线程从队列拥有者相反的方向进行操作会减少线程竞争。同样体现了递归分治算法的大任务优先策略。因此,更早期被取的任务有可能会提供一个更大的单元任务,从而使得取线程能够在将来进行递归分解。
作为上述规则的一个后果,对于一些基础的操作而言,使用相对较小粒度的任务比那些仅仅使用粗粒度划分的任务以及那些没有使用递归分解的任务的运行速度要快。尽管相关的少数任务在大多数的`Fork/Join`框架中会被其他工作线程取,但是创建许多组织良好的任务意味着只要有一个工作线程处于可运行的状态,那么这个任务就有可能被执行。
作为上述规则的一个后果,对于一些基础的操作而言,使用相对较小粒度的任务比那些仅仅使用粗粒度划分的任务以及那些没有使用递归分解的任务的运行速度要快。尽管相关的少数任务在大多数的`Fork/Join`框架中会被其他工作线程取,但是创建许多组织良好的任务意味着只要有一个工作线程处于可运行的状态,那么这个任务就有可能被执行。
# 3. 实现
这个框架是由大约800行纯`Java`代码组成,主要的类是`FJTaskRunner`,它是`java.lang.Thread`的子类。`FJTask`自己仅仅维持一个关于结束状态的布尔值,所有其他的操作都是通过当前的工作线程来代理完成的。`JFTaskRunnerGroup`类用于创建工作线程,维护一些共享的状态(例如:所有工作线程的标示符,在取操作时需要),同时还要协调启动和关闭。
这个框架是由大约800行纯`Java`代码组成,主要的类是`FJTaskRunner`,它是`java.lang.Thread`的子类。`FJTask`自己仅仅维持一个关于结束状态的布尔值,所有其他的操作都是通过当前的工作线程来代理完成的。`JFTaskRunnerGroup`类用于创建工作线程,维护一些共享的状态(例如:所有工作线程的标示符,在取操作时需要),同时还要协调启动和关闭。
更多实现的细节文档可以在`util.concurrent`并发包中查看。这一节只着重讨论两类问题以及在实现这个框架的时候所形成的一些解决方案:支持高效的双端列表操作(`push``pop``take` 并且当工作线程在尝试获取新的任务时维持取的协议。
更多实现的细节文档可以在`util.concurrent`并发包中查看。这一节只着重讨论两类问题以及在实现这个框架的时候所形成的一些解决方案:支持高效的双端列表操作(`push``pop``take` 并且当工作线程在尝试获取新的任务时维持取的协议。
## 3.1 双端队列
@@ -166,7 +166,7 @@ _校注双端队列中的元素可以从两端弹出其限定插入和
实现双端队列的主要挑战来自于同步和他的撤销。尽管在`Java`虚拟机上使用经过优化过的同步工具,对于每个`push``pop`操作都需要获取锁还是让这一切成为性能瓶颈。然后根据以下的观察结果我们可以修改`Clik`中的策略,从而为我们提供一种可行的解决方案:
- `push``pop`操作仅可以被工作线程的拥有者所调用。
-`take`的操作很容易会由于取任务线程在某一时间对`take`操作加锁而限制。(双端队列在必要的时间也可以禁止`take`操作。)这样,控制冲突将被降低为两个部分同步的层次。
-`take`的操作很容易会由于取任务线程在某一时间对`take`操作加锁而限制。(双端队列在必要的时间也可以禁止`take`操作。)这样,控制冲突将被降低为两个部分同步的层次。
- `pop``take`操作只有在双端队列为空的时候才会发生冲突,否则的话,队列会保证他们在不同的数组元素上面操作。
`top``base`索引定义为`volatile`变量可以保证当队列中元素不止一个时,`pop``take`操作可以在不加锁的情况下进行。这是通过一种类似于`Dekker`算法来实现的。当`push`预递减到`top`时:
@@ -219,7 +219,7 @@ if (++base < top) ...
通过使用不同数目1 \~ 30的工作线程对同一问题集进行测试用来得到框架的扩展性测试结果。虽然我们无法保证`Java`虚拟机是否总是能够将每一个线程映射到不同的空闲`CPU`上,同时,我们也没有证据来证明这点。有可能映射一个新的线程到`CPU`的延迟会随着线程数目的增加而变大,也可能会随不同的系统以及不同的测试程序而变化。但是,所得到的测试结果的确显示出增加线程的数目确实能够增加使用的`CPU`的数目。
加速比通常表示为 _<code>Time<sub>n</sub> / Time<sub>1<sub></code>_。如上图所示其中求积分的程序表现出最好的加速比30个线程的加速比为28.2表现最差的是矩阵分解程序30线程是加速比只有15.35
加速比通常表示为 **_<code>Time<sub>n</sub> / Time<sub>1<sub></code>_**。如上图所示其中求积分的程序表现出最好的加速比30个线程的加速比为28.2表现最差的是矩阵分解程序30线程是加速比只有15.35
另一种衡量扩展性的依据是:任务执行率,及执行一个单独任务(这里的任务有可能是递归分解节点任务也可能是根节点任务)所开销的平均时间。下面的数据显示出一次性执行各个程序所得到的任务执行率数据。很明显,单位时间内执行的任务数目应该是固定常量。然而事实上,随着线程数目增加,所得到的数据会表现出轻微的降低,这也表现出其一定的扩展性限制。这里需要说明的是,之所以任务执行率在各个程序上表现的巨大差异,是因其任务粒度的不同造成的。任务执行率最小的程序是`Fib`菲波那契数列其阀值设置为13在30个线程的情况下总共完成了280万个单元任务。