「这是我参与2022首次更文挑战的第 15 天,活动详情查看:2022首次更文挑战」。
这些是关于 parallel iterator trait 设计的一些简要说明。本文没有描述如何使用并行迭代器。如有需要,请浏览 ParallelIterator 。
挑战
parallel iterator 比顺序迭代器更复杂。原因在于:它们必须能够将自己分割,并在分割出来部分之间并行操作。
目前对 parallel iterator 的设计有两种不同的使用模式;正如我们将看到的,并非所有的迭代器都支持两种模式。下面说说为什么有两种模式:
Pull
Producer以及UnindexedProducerTrait
在这种模式下,要求迭代器通过调用 next() 来产生下一个item。这基本上就像一个普通的迭代器,但有一个不同:你可以将迭代器分割成两部分,在不同的线程中产生不相干的item。
分别描述一下上述两个 Trait:
在 Producer Trait 中,分割是通过 split_at() 完成的,它接受一个应该执行分割的index。只有具有 index 的迭代器可以在这种模式下工作,因为它们清楚地知道它们将产生多少数据,以及如何定位访问的 index。
在 UnindexedProducer Trait 中,分割是通过 split() 完成的,它只是要求生产者将自己大概分割成两部分。当长度或内存布局未知时,如字符串,或者当长度可能超过usize时,如: 32位平台上的 Range<u64>,这很有用。
理论上讲,任何Producer都可以采取非索引的方式,但我们目前没有使用这种。当你知道确切的长度时,split() 就可以简单地实现为 split_at(length/2)。
Push
Consumer以及UnindexedConsumerTrait
在这种模式下,迭代器会依次给出每个 item,然后进行处理。这与普通的迭代器相反。它更像是for_each 的调用:每次产生一个新的 item,都会用这个 item 调用 consumer() (特质本身要复杂一些,因为它们支持可以被贯穿并最终减少的状态)。与生产者不同,消费者有两种变体,区别在于如何进行分割。
在 Consumer trait 中,分割是通过 split_at 来完成的,它接受一个index用来分割数据。所有的迭代器都可以在这种模式下工作。这样一来,分割产生的部分就对他们自己消费数据量有个基本的评估。
在 UnindexedConsumer trait 中,分割是通过 split_off_left() 完成的。这种情况下是没有索引:分割产生的部分得自己准备好处理数量未知的数据流,而且它们并不知道这些数据在整个数据流中的位置。
对于 UnindexedConsumer trait,不是所有的消费者都能在这种模式下操作。例如,它适用于for_each 和 reduce,但是它不适用于 collect_into_vec,因为在这种情况下,每个item是依赖于它在目标集合中的最终位置。