Skip to main content

orx_concurrent_iter/
concurrent_iter.rs

1use crate::{
2    IntoConcurrentIter,
3    chain::ChainUnknownLenI,
4    cloned::ConIterCloned,
5    copied::ConIterCopied,
6    enumerate::Enumerate,
7    pullers::{ChunkPuller, EnumeratedItemPuller, ItemPuller},
8};
9
10/// An iterator which can safely be used concurrently by multiple threads.
11///
12/// This trait can be considered as the *concurrent counterpart* of the [`Iterator`]
13/// trait.
14///
15/// Practically, this means that elements can be pulled using a shared reference,
16/// and therefore, it can be conveniently shared among threads.
17///
18/// # Examples
19///
20/// ## A. while let loops: next & next_with_idx
21///
22/// Main method of a concurrent iterator is the [`next`] which is identical to the
23/// `Iterator::next` method except that it requires a shared reference.
24/// Additionally, [`next_with_idx`] can be used whenever the index of the element
25/// is also required.
26///
27/// [`next`]: crate::ConcurrentIter::next
28/// [`next_with_idx`]: crate::ConcurrentIter::next_with_idx
29///
30/// ```
31/// use orx_concurrent_iter::*;
32///
33/// let vec = vec!['x', 'y'];
34/// let con_iter = vec.con_iter();
35/// assert_eq!(con_iter.next(), Some(&'x'));
36/// assert_eq!(con_iter.next_with_idx(), Some((1, &'y')));
37/// assert_eq!(con_iter.next(), None);
38/// assert_eq!(con_iter.next_with_idx(), None);
39/// ```
40///
41/// This iteration methods yielding optional elements can be used conveniently with
42/// `while let` loops.
43///
44/// In the following program 100 strings in the vector will be processed concurrently
45/// by four threads. Note that this is a very convenient but effective way to share
46/// tasks among threads especially in heterogeneous scenarios. Every time a thread
47/// completes processing a value, it will pull a new element (task) from the iterator.
48///
49/// ```
50/// use orx_concurrent_iter::*;
51///
52/// let num_threads = 4;
53/// let data: Vec<_> = (0..100).map(|x| x.to_string()).collect();
54/// let con_iter = data.con_iter();
55///
56/// let process = |_x: &String| { /* assume actual work */ };
57///
58/// std::thread::scope(|s| {
59///     for _ in 0..num_threads {
60///         s.spawn(|| {
61///             // concurrently iterate over values in a `while let` loop
62///             while let Some(value) = con_iter.next() {
63///                 process(value);
64///             }
65///         });
66///     }
67/// });
68/// ```
69///
70/// ## B. for loops: item_puller
71///
72/// Although `while let` loops are considerably convenient, a concurrent iterator
73/// cannot be directly used with `for` loops. However, it is possible to create a
74/// regular Iterator from a concurrent iterator within a thread which can safely
75/// **pull** elements from the concurrent iterator. Since it is a regular Iterator,
76/// it can be used with a `for` loop.
77///
78/// The regular Iterator; i.e., the puller can be created using the [`item_puller`]
79/// method. Alternatively, [`item_puller_with_idx`] can be used to create an iterator
80/// which also yields the indices of the items.
81///
82/// Therefore, the parallel processing example above can equivalently implemented
83/// as follows.
84///
85/// [`item_puller`]: crate::ConcurrentIter::item_puller
86/// [`item_puller_with_idx`]: crate::ConcurrentIter::item_puller_with_idx
87///
88/// ```
89/// use orx_concurrent_iter::*;
90///
91/// let num_threads = 4;
92/// let data: Vec<_> = (0..100).map(|x| x.to_string()).collect();
93/// let con_iter = data.con_iter();
94///
95/// let process = |_x: &String| { /* assume actual work */ };
96///
97/// std::thread::scope(|s| {
98///     for _ in 0..num_threads {
99///         s.spawn(|| {
100///             // concurrently iterate over values in a `for` loop
101///             for value in con_iter.item_puller() {
102///                 process(value);
103///             }
104///         });
105///     }
106/// });
107/// ```
108///
109/// It is important to emphasize that the [`ItemPuller`] implements a regular [`Iterator`].
110/// This not only enables the `for` loops but also makes all iterator methods available.
111///
112/// The following simple yet efficient implementation of the parallelized version of the
113/// [`reduce`] demonstrates the convenience of the pullers. Notice that the entire
114/// implementation of the `parallel_reduce` is nothing but a chain of iterator methods.
115///
116/// ```
117/// use orx_concurrent_iter::*;
118///
119/// fn parallel_reduce<T, F>(
120///     num_threads: usize,
121///     chunk: usize,
122///     con_iter: impl ConcurrentIter<Item = T>,
123///     reduce: F,
124/// ) -> Option<T>
125/// where
126///     T: Send,
127///     F: Fn(T, T) -> T + Sync,
128/// {
129///     std::thread::scope(|s| {
130///         (0..num_threads)
131///             .map(|_| s.spawn(|| con_iter.chunk_puller(chunk).flattened().reduce(&reduce))) // reduce inside each thread
132///             .filter_map(|x| x.join().unwrap()) // join threads, ignore None's
133///             .reduce(&reduce) // reduce thread results to final result
134///     })
135/// }
136///
137/// let n = 10_000;
138/// let data: Vec<_> = (0..n).collect();
139/// let sum = parallel_reduce(8, 64, data.con_iter().copied(), |a, b| a + b);
140/// assert_eq!(sum, Some(n * (n - 1) / 2));
141/// ```
142///
143/// [`ItemPuller`]: crate::ItemPuller
144/// [`reduce`]: Iterator::reduce
145///
146/// ## C. Iteration by Chunks
147///
148/// Iteration using `next`, `next_with_idx` or via the pullers created by `item_puller`
149/// or `item_puller_with_idx` all pull elements from the data source one by one.
150/// This is exactly similar to iteration by a regular Iterator. However, depending on the
151/// use case, this is not always what we want in a concurrent program.
152///
153/// Due to the following reason.
154///
155/// Concurrent iterators use atomic variables which have an overhead compared to sequential
156/// iterators. Every time we pull an element from a concurrent iterator, its atomic state is
157/// updated. Therefore, the fewer times we update the atomic state, the less significant the
158/// overhead. The way to achieve fewer updates is through pulling multiple elements at once,
159/// rather than one element at a time.
160/// * Note that this can be considered as an optimization technique which might or might
161///   not be relevant. The rule of thumb is as follows; the more work we do on each element
162///   (or equivalently, the larger the `process` is), the less significant the overhead is.
163///
164/// Nevertheless, it is conveniently possible to achieve fewer updates using chunk pullers.
165/// A chunk puller is similar to the item puller except that it pulls multiple elements at
166/// once. A chunk puller can be created from a concurrent iterator using the [`chunk_puller`]
167/// method.
168///
169/// The following program uses a chunk puller. Chunk puller's [`pull`] method returns an option
170/// of an [`ExactSizeIterator`]. The `ExactSizeIterator` will contain 10 elements, or less if
171/// not left enough, but never 0 elements (in this case `pull` returns None). This allows for
172/// using a `while let` loop. Then, we can iterate over the `chunk` which is a regular iterator.
173///
174/// Note that, we can also use [`pull_with_idx`] whenever the indices are also required.
175///
176/// [`chunk_puller`]: crate::ConcurrentIter::chunk_puller
177/// [`pull`]: crate::ChunkPuller::pull
178/// [`pull_with_idx`]: crate::ChunkPuller::pull_with_idx
179///
180/// ```
181/// use orx_concurrent_iter::*;
182///
183/// let num_threads = 4;
184/// let data: Vec<_> = (0..100).map(|x| x.to_string()).collect();
185/// let con_iter = data.con_iter();
186///
187/// let process = |_x: &String| {};
188///
189/// std::thread::scope(|s| {
190///     for _ in 0..num_threads {
191///         s.spawn(|| {
192///             // concurrently iterate over values in a `while let` loop
193///             // while pulling (up to) 10 elements every time
194///             let mut chunk_puller = con_iter.chunk_puller(10);
195///             while let Some(chunk) = chunk_puller.pull() {
196///                 // chunk is an ExactSizeIterator
197///                 for value in chunk {
198///                     process(value);
199///                 }
200///             }
201///         });
202///     }
203/// });
204/// ```
205///
206/// ## D. Iteration by Flattened Chunks
207///
208/// The above code conveniently allows for the iteration-by-chunks optimization.
209/// However, you might have noticed that now we have a nested `while let` and `for` loops.
210/// In terms of convenience, we can do better than this without losing any performance.
211///
212/// This can be achieved using the [`flattened`] method of the chunk puller (see also
213/// [`flattened_with_idx`]).
214///
215/// [`flattened`]: crate::ChunkPuller::flattened
216/// [`flattened_with_idx`]: crate::ChunkPuller::flattened_with_idx
217///
218/// ```
219/// use orx_concurrent_iter::*;
220///
221/// let num_threads = 4;
222/// let data: Vec<_> = (0..100).map(|x| x.to_string()).collect();
223/// let con_iter = data.con_iter();
224///
225/// let process = |_x: &String| {};
226///
227/// std::thread::scope(|s| {
228///     for _ in 0..num_threads {
229///         s.spawn(|| {
230///             // concurrently iterate over values in a `for` loop
231///             // while concurrently pulling (up to) 10 elements every time
232///             for value in con_iter.chunk_puller(10).flattened() {
233///                 process(value);
234///             }
235///         });
236///     }
237/// });
238/// ```
239///
240/// A bit of magic here, that requires to be explained below.
241///
242/// Notice that this is a very convenient way to concurrently iterate over the elements
243/// using a simple `for` loop. However, it is important to note that, under the hood, this is
244/// equivalent to the program in the previous section where we used the `pull` method of the
245/// chunk puller.
246///
247/// The following happens under the hood:
248///
249/// * We reach the concurrent iterator to pull 10 items at once from the data source.
250///   This is the intended performance optimization to reduce the updates of the atomic state.
251/// * Then, we iterate one-by-one over the pulled 10 items inside the thread as a regular iterator.
252/// * Once, we complete processing these 10 items, we approach the concurrent iterator again.
253///   Provided that there are elements left, we pull another chunk of 10 items.
254/// * Then, we iterate one-by-one ...
255///
256/// It is important to note that, when we say we pull 10 items, we actually only reserve these
257/// elements for the corresponding thread. We do not actually clone elements or copy memory.
258///
259/// ## E. Early Exit
260///
261/// Concurrent iterators also support early exit scenarios through a simple method call,
262/// [`skip_to_end`]. Whenever, any of the threads observes a certain condition and decides that
263/// it is no longer necessary to iterate over the remaining elements, it can call `skip_to_end`.
264///
265/// Threads approaching the concurrent iterator to pull more elements after this call will
266/// observe that there are no other elements left and may exit.
267///
268/// One common use case is the `find` method of iterators. The following is a parallel implementation
269/// of `find` using concurrent iterators.
270///
271/// In the following program, one of the threads will find "33" satisfying the predicate and will call
272/// `skip_to_end` to jump to end of the iterator. In the example setting, it is possible that other threads
273/// might still process some more items:
274///
275/// * Just while the thread that found "33" is evaluating the predicate, other threads might pull a
276///   few more items, say 34, 35 and 36.
277/// * While they might be comparing these items against the predicate, the winner thread calls `skip_to_end`.
278/// * After this point, the item pullers' next calls will all return None.
279/// * This will allow all threads to return & join, without actually going through all 1000 elements of the
280///   data source.
281///
282/// In this regard, `skip_to_end` allows for a little communication among threads in early exit scenarios.
283///
284/// [`skip_to_end`]: crate::ConcurrentIter::skip_to_end
285///
286/// ```
287/// use orx_concurrent_iter::*;
288///
289/// fn parallel_find<T, F>(
290///     num_threads: usize,
291///     con_iter: impl ConcurrentIter<Item = T>,
292///     predicate: F,
293/// ) -> Option<T>
294/// where
295///     T: Send,
296///     F: Fn(&T) -> bool + Sync,
297/// {
298///     std::thread::scope(|s| {
299///         (0..num_threads)
300///             .map(|_| {
301///                 s.spawn(|| {
302///                     con_iter
303///                         .item_puller()
304///                         .find(&predicate)
305///                         // once found, immediately jump to end
306///                         .inspect(|_| con_iter.skip_to_end())
307///                 })
308///             })
309///             .filter_map(|x| x.join().unwrap())
310///             .next()
311///     })
312/// }
313///
314/// let data: Vec<_> = (0..1000).map(|x| x.to_string()).collect();
315/// let value = parallel_find(4, data.con_iter(), |x| x.starts_with("33"));
316///
317/// assert_eq!(value, Some(&33.to_string()));
318/// ```
319///
320/// ## F. Back to Sequential Iterator
321///
322/// Every concurrent iterator can be consumed and converted into a regular sequential
323/// iterator using [`into_seq_iter`] method. In this sense, it can be considered as a
324/// generalization of iterators that can be iterated over either concurrently or sequentially.
325///
326/// [`into_seq_iter`]: crate::ConcurrentIter::into_seq_iter
327pub trait ConcurrentIter: Sync {
328    /// Type of the element that the concurrent iterator yields.
329    type Item: Send;
330
331    /// Type of the sequential iterator that the concurrent iterator can be converted
332    /// into using the [`into_seq_iter`] method.
333    ///
334    /// [`into_seq_iter`]: crate::ConcurrentIter::into_seq_iter
335    type SequentialIter: Iterator<Item = Self::Item>;
336
337    /// Type of the chunk puller that can be created using the [`chunk_puller`] method.
338    ///
339    /// [`chunk_puller`]: crate::ConcurrentIter::chunk_puller
340    type ChunkPuller<'i>: ChunkPuller<ChunkItem = Self::Item>
341    where
342        Self: 'i;
343
344    /// Returns whether producing source items is serialized across threads.
345    ///
346    /// This is used by parallel runners to avoid per-item exploration when the
347    /// source cannot be split and every pull requires exclusive access.
348    fn is_source_serialized() -> bool;
349
350    // transform
351
352    /// Converts the concurrent iterator into its sequential regular counterpart.
353    /// Note that the sequential iterator is a regular [`Iterator`], and hence,
354    /// does not have any overhead related with atomic states. Therefore, it is
355    /// useful where the program decides to iterate over a single thread rather
356    /// than concurrently by multiple threads.
357    ///
358    /// # Examples
359    ///
360    /// ```
361    /// use orx_concurrent_iter::*;
362    ///
363    /// let data = vec!['x', 'y'];
364    ///
365    /// // con_iter implements ConcurrentIter
366    /// let con_iter = data.into_con_iter();
367    ///
368    /// // seq_iter implements regular Iterator
369    /// // it has the same type as the iterator we would
370    /// // have got with `data.into_iter()`
371    /// let mut seq_iter = con_iter.into_seq_iter();
372    /// assert_eq!(seq_iter.next(), Some('x'));
373    /// assert_eq!(seq_iter.next(), Some('y'));
374    /// assert_eq!(seq_iter.next(), None);
375    /// ```
376    fn into_seq_iter(self) -> Self::SequentialIter;
377
378    // iterate
379
380    /// Immediately jumps to the end of the iterator, skipping the remaining elements.
381    ///
382    /// This method is useful in early-exit scenarios which allows not only the thread
383    /// calling this method to return early, but also all other threads that are iterating
384    /// over this concurrent iterator to return early since they would not find any more
385    /// remaining elements.
386    ///
387    /// # Example
388    ///
389    /// One common use case is the `find` method of iterators. The following is a parallel implementation
390    /// of `find` using concurrent iterators.
391    ///
392    /// In the following program, one of the threads will find "33" satisfying the predicate and will call
393    /// `skip_to_end` to jump to end of the iterator. In the example setting, it is possible that other threads
394    /// might still process some more items:
395    ///
396    /// * Just while the thread that found "33" is evaluating the predicate, other threads might pull a
397    ///   few more items, say 34, 35 and 36.
398    /// * While they might be comparing these items against the predicate, the winner thread calls `skip_to_end`.
399    /// * After this point, the item pullers' next calls will all return None.
400    /// * This will allow all threads to return & join, without actually going through all 1000 elements of the
401    ///   data source.
402    ///
403    /// In this regard, `skip_to_end` allows for a little communication among threads in early exit scenarios.
404    ///
405    /// [`skip_to_end`]: crate::ConcurrentIter::skip_to_end
406    ///
407    /// ```
408    /// use orx_concurrent_iter::*;
409    ///
410    /// fn parallel_find<T, F>(
411    ///     num_threads: usize,
412    ///     con_iter: impl ConcurrentIter<Item = T>,
413    ///     predicate: F,
414    /// ) -> Option<T>
415    /// where
416    ///     T: Send,
417    ///     F: Fn(&T) -> bool + Sync,
418    /// {
419    ///     std::thread::scope(|s| {
420    ///         (0..num_threads)
421    ///             .map(|_| {
422    ///                 s.spawn(|| {
423    ///                     con_iter
424    ///                         .item_puller()
425    ///                         .find(&predicate)
426    ///                         // once found, immediately jump to end
427    ///                         .inspect(|_| con_iter.skip_to_end())
428    ///                 })
429    ///             })
430    ///             .filter_map(|x| x.join().unwrap())
431    ///             .next()
432    ///     })
433    /// }
434    ///
435    /// let data: Vec<_> = (0..1000).map(|x| x.to_string()).collect();
436    /// let value = parallel_find(4, data.con_iter(), |x| x.starts_with("33"));
437    ///
438    /// assert_eq!(value, Some(&33.to_string()));
439    /// ```
440    fn skip_to_end(&self);
441
442    /// Returns the next element of the iterator.
443    /// It returns None if there are no more elements left.
444    ///
445    /// Notice that this method requires a shared reference rather than a mutable reference, and hence,
446    /// can be called concurrently from multiple threads.
447    ///
448    /// See also [`next_with_idx`] in order to receive additionally the index of the elements.
449    ///
450    /// [`next_with_idx`]: crate::ConcurrentIter::next_with_idx
451    ///
452    /// # Examples
453    ///
454    /// ```
455    /// use orx_concurrent_iter::*;
456    ///
457    /// let vec = vec!['x', 'y'];
458    /// let con_iter = vec.con_iter();
459    /// assert_eq!(con_iter.next(), Some(&'x'));
460    /// assert_eq!(con_iter.next(), Some(&'y'));
461    /// assert_eq!(con_iter.next(), None);
462    /// ```
463    ///
464    /// This iteration methods yielding optional elements can be used conveniently with
465    /// `while let` loops.
466    ///
467    /// In the following program 100 strings in the vector will be processed concurrently
468    /// by four threads. Note that this is a very convenient but effective way to share
469    /// tasks among threads especially in heterogeneous scenarios. Every time a thread
470    /// completes processing a value, it will pull a new element (task) from the iterator.
471    ///
472    /// ```
473    /// use orx_concurrent_iter::*;
474    ///
475    /// let num_threads = 4;
476    /// let data: Vec<_> = (0..100).map(|x| x.to_string()).collect();
477    /// let con_iter = data.con_iter();
478    ///
479    /// let process = |_x: &String| { /* assume actual work */ };
480    ///
481    /// std::thread::scope(|s| {
482    ///     for _ in 0..num_threads {
483    ///         s.spawn(|| {
484    ///             // concurrently iterate over values in a `while let` loop
485    ///             while let Some(value) = con_iter.next() {
486    ///                 process(value);
487    ///             }
488    ///         });
489    ///     }
490    /// });
491    /// ```
492    fn next(&self) -> Option<Self::Item>;
493
494    /// Behaves exactly as [`next`] but additionally provides `thread_idx` to the iterator.
495    /// This information might be useful for certain concurrent iterators, such as the
496    /// [recursive concurrent iterator](https://crates.io/crates/orx-concurrent-recursive-iter).
497    ///
498    /// Assuming a program using `n` threads that accesses this iterator, `thread_idx` is
499    /// assumed to be the internal ordering within this pool of threads taking values in
500    /// `0..n`.
501    ///
502    /// [`next`]: Self::next
503    #[inline(always)]
504    #[allow(unused_variables)]
505    fn next_by(&self, thread_idx: usize) -> Option<Self::Item> {
506        self.next()
507    }
508
509    /// Returns the next element of the iterator together its index.
510    /// It returns None if there are no more elements left.
511    ///
512    /// See also [`enumerate`] to convert the concurrent iterator into its enumerated
513    /// counterpart.
514    ///
515    /// [`enumerate`]: crate::ConcurrentIter::enumerate
516    ///
517    /// # Examples
518    ///
519    /// ```
520    /// use orx_concurrent_iter::*;
521    ///
522    /// let vec = vec!['x', 'y'];
523    /// let con_iter = vec.con_iter();
524    /// assert_eq!(con_iter.next_with_idx(), Some((0, &'x')));
525    /// assert_eq!(con_iter.next_with_idx(), Some((1, &'y')));
526    /// assert_eq!(con_iter.next_with_idx(), None);
527    /// ```
528    fn next_with_idx(&self) -> Option<(usize, Self::Item)>;
529
530    /// Behaves exactly as [`next_with_idx`] but additionally provides `thread_idx` to the iterator.
531    /// This information might be useful for certain concurrent iterators, such as the
532    /// [recursive concurrent iterator](https://crates.io/crates/orx-concurrent-recursive-iter).
533    ///
534    /// Assuming a program using `n` threads that accesses this iterator, `thread_idx` is
535    /// assumed to be the internal ordering within this pool of threads taking values in
536    /// `0..n`.
537    ///
538    /// [`next_with_idx`]: Self::next_with_idx
539    #[inline(always)]
540    #[allow(unused_variables)]
541    fn next_with_idx_by(&self, thread_idx: usize) -> Option<(usize, Self::Item)> {
542        self.next_with_idx()
543    }
544
545    // len
546
547    /// Returns the bounds on the remaining length of the iterator.
548    ///
549    /// The first element is the lower bound, and the second element is the upper bound.
550    ///
551    /// Having an upper bound of None means that there is no knowledge of a limit of the number of
552    /// remaining elements.
553    ///
554    /// Having a tuple of `(x, Some(x))` means that, we are certain about the number of remaining
555    /// elements, which `x`. When the concurrent iterator additionally implements [`ExactSizeConcurrentIter`],
556    /// then its `len` method also returns `x`.
557    ///
558    /// [`ExactSizeConcurrentIter`]: crate::ExactSizeConcurrentIter
559    ///
560    /// # Examples
561    ///
562    /// ```
563    /// use orx_concurrent_iter::*;
564    ///
565    /// // implements ExactSizeConcurrentIter
566    ///
567    /// let data = vec!['x', 'y', 'z'];
568    /// let con_iter = data.con_iter();
569    /// assert_eq!(con_iter.size_hint(), (3, Some(3)));
570    /// assert_eq!(con_iter.len(), 3);
571    ///
572    /// assert_eq!(con_iter.next(), Some(&'x'));
573    /// assert_eq!(con_iter.size_hint(), (2, Some(2)));
574    /// assert_eq!(con_iter.len(), 2);
575    ///
576    /// // does not implement ExactSizeConcurrentIter
577    ///
578    /// let iter = data.iter().filter(|x| **x != 'y');
579    /// let con_iter = iter.iter_into_con_iter();
580    /// assert_eq!(con_iter.size_hint(), (0, Some(3)));
581    ///
582    /// assert_eq!(con_iter.next(), Some(&'x'));
583    /// assert_eq!(con_iter.size_hint(), (0, Some(2)));
584    ///
585    /// assert_eq!(con_iter.next(), Some(&'z'));
586    /// assert_eq!(con_iter.size_hint(), (0, Some(0)));
587    /// ```
588    fn size_hint(&self) -> (usize, Option<usize>);
589
590    /// Returns `Some(x)` if the number of remaining items is known with certainly and if it
591    /// is equal to `x`.
592    ///
593    /// It returns None otherwise.
594    ///
595    /// Note that this is a shorthand for:
596    ///
597    /// ```ignore
598    /// match con_iter.size_hint() {
599    ///     (x, Some(y)) if x == y => Some(x),
600    ///     _ => None,
601    /// }
602    /// ```
603    fn try_get_len(&self) -> Option<usize> {
604        match self.size_hint() {
605            (x, Some(y)) if x == y => Some(x),
606            _ => None,
607        }
608    }
609
610    /// Returns true if the concurrent iterator which has returned `None` for a [`next`]
611    /// or [`pull`] call will continue to return `None`.
612    ///
613    /// Note that most concurrent iterators shared the behavior of a [`FusedIterator`];
614    /// therefore, this method returns `true` in most of the cases.
615    ///
616    /// However, there are dynamic or recursive iterators which can concurrently grow,
617    /// while at the same time we are pulling elements from it. In such a concurrent iterator,
618    /// there might be an instant where `next` returns `None` while another thread is adding
619    /// elements to the concurrent iterator. This means that a future `next` call will return
620    /// `Some(element)`. This method is useful for such iterators. We can stop trying to pull
621    /// elements if we receive a `None` and `is_completed_when_none_returned` returns `true`.
622    /// If we receive a `None` but `is_completed_when_none_returned` returns `false`, it is
623    /// possible that a future try will return an element.
624    ///
625    /// Such an example concurrent iterator is the
626    /// [`ConcurrentRecursiveIter`](https://crates.io/crates/orx-concurrent-recursive-iter).
627    /// In this recursive iterator, each pulled element might add some elements to the end
628    /// of the iterator. Pulling of elements and expansion happens concurrently.
629    ///
630    /// [`next`]: ConcurrentIter::next
631    /// [`pull`]: ChunkPuller::pull
632    /// [`FusedIterator`]: core::iter::FusedIterator
633    fn is_completed_when_none_returned(&self) -> bool;
634
635    // pullers
636
637    /// Creates a [`ChunkPuller`] from the concurrent iterator.
638    /// The created chunk puller can be used to [`pull`] `chunk_size` elements at once from the
639    /// data source, rather than pulling one by one.
640    ///
641    /// Iterating over chunks using a chunk puller rather than single elements is an optimization
642    /// technique. Chunk pullers enable a convenient way to apply this optimization technique
643    /// which is not relevant for certain scenarios, while it is very effective for others.
644    ///
645    /// The reason why we would want to iterate over chunks is as follows.
646    ///
647    /// Concurrent iterators use atomic variables which have an overhead compared to sequential
648    /// iterators. Every time we pull an element from a concurrent iterator, its atomic state is
649    /// updated. Therefore, the fewer times we update the atomic state, the less significant the
650    /// overhead. The way to achieve fewer updates is through pulling multiple elements at once,
651    /// rather than one element at a time.
652    /// * The more work we do on each element, the less significant the overhead is.
653    ///
654    /// Nevertheless, it is conveniently possible to achieve fewer updates using chunk pullers.
655    /// A chunk puller is similar to the item puller except that it pulls multiple elements at
656    /// once.
657    ///
658    /// The following program uses a chunk puller. Chunk puller's [`pull`] method returns an option
659    /// of an [`ExactSizeIterator`]. The `ExactSizeIterator` will contain 10 elements, or less if
660    /// not left enough, but never 0 elements (in this case `pull` returns None). This allows for
661    /// using a `while let` loop. Then, we can iterate over the `chunk` which is a regular iterator.
662    ///
663    /// Note that, we can also use [`pull_with_idx`] whenever the indices are also required.
664    ///
665    /// [`chunk_puller`]: crate::ConcurrentIter::chunk_puller
666    /// [`pull`]: crate::ChunkPuller::pull
667    /// [`pull_with_idx`]: crate::ChunkPuller::pull_with_idx
668    /// [`ChunkPuller`]: crate::ChunkPuller
669    /// [`pull`]: crate::ChunkPuller::pull
670    ///
671    /// # Examples
672    ///
673    /// ## Iteration by Chunks
674    ///
675    /// ```
676    /// use orx_concurrent_iter::*;
677    ///
678    /// let num_threads = 4;
679    /// let data: Vec<_> = (0..100).map(|x| x.to_string()).collect();
680    /// let con_iter = data.con_iter();
681    ///
682    /// let process = |_x: &String| {};
683    ///
684    /// std::thread::scope(|s| {
685    ///     for _ in 0..num_threads {
686    ///         s.spawn(|| {
687    ///             // concurrently iterate over values in a `while let` loop
688    ///             // while pulling (up to) 10 elements every time
689    ///             let mut chunk_puller = con_iter.chunk_puller(10);
690    ///             while let Some(chunk) = chunk_puller.pull() {
691    ///                 // chunk is an ExactSizeIterator
692    ///                 for value in chunk {
693    ///                     process(value);
694    ///                 }
695    ///             }
696    ///         });
697    ///     }
698    /// });
699    /// ```
700    ///
701    /// ## Iteration by Flattened Chunks
702    ///
703    /// The above code conveniently allows for the iteration-by-chunks optimization.
704    /// However, you might have noticed that now we have a nested `while let` and `for` loops.
705    /// In terms of convenience, we can do better than this without losing any performance.
706    ///
707    /// This can be achieved using the [`flattened`] method of the chunk puller (see also
708    /// [`flattened_with_idx`]).
709    ///
710    /// [`flattened`]: crate::ChunkPuller::flattened
711    /// [`flattened_with_idx`]: crate::ChunkPuller::flattened_with_idx
712    ///
713    /// ```
714    /// use orx_concurrent_iter::*;
715    ///
716    /// let num_threads = 4;
717    /// let data: Vec<_> = (0..100).map(|x| x.to_string()).collect();
718    /// let con_iter = data.con_iter();
719    ///
720    /// let process = |_x: &String| {};
721    ///
722    /// std::thread::scope(|s| {
723    ///     for _ in 0..num_threads {
724    ///         s.spawn(|| {
725    ///             // concurrently iterate over values in a `for` loop
726    ///             // while concurrently pulling (up to) 10 elements every time
727    ///             for value in con_iter.chunk_puller(10).flattened() {
728    ///                 process(value);
729    ///             }
730    ///         });
731    ///     }
732    /// });
733    /// ```
734    ///
735    /// A bit of magic here, that requires to be explained below.
736    ///
737    /// Notice that this is a very convenient way to concurrently iterate over the elements
738    /// using a simple `for` loop. However, it is important to note that, under the hood, this is
739    /// equivalent to the program in the previous section where we used the `pull` method of the
740    /// chunk puller.
741    ///
742    /// The following happens under the hood:
743    ///
744    /// * We reach the concurrent iterator to pull 10 items at once from the data source.
745    ///   This is the intended performance optimization to reduce the updates of the atomic state.
746    /// * Then, we iterate one-by-one over the pulled 10 items inside the thread as a regular iterator.
747    /// * Once, we complete processing these 10 items, we approach the concurrent iterator again.
748    ///   Provided that there are elements left, we pull another chunk of 10 items.
749    /// * Then, we iterate one-by-one ...
750    ///
751    /// It is important to note that, when we say we pull 10 items, we actually only reserve these
752    /// elements for the corresponding thread. We do not actually clone elements or copy memory.
753    fn chunk_puller(&self, chunk_size: usize) -> Self::ChunkPuller<'_>;
754
755    /// Behaves exactly as [`chunk_puller`] but additionally provides `thread_idx` to the iterator.
756    /// This information might be useful for certain concurrent iterators, such as the
757    /// [recursive concurrent iterator](https://crates.io/crates/orx-concurrent-recursive-iter).
758    ///
759    /// Assuming a program using `n` threads that accesses this iterator, `thread_idx` is
760    /// assumed to be the internal ordering within this pool of threads taking values in
761    /// `0..n`.
762    ///
763    /// [`chunk_puller`]: Self::chunk_puller
764    #[inline(always)]
765    #[allow(unused_variables)]
766    fn chunk_puller_by(&self, chunk_size: usize, thread_idx: usize) -> Self::ChunkPuller<'_> {
767        self.chunk_puller(chunk_size)
768    }
769
770    /// Creates a [`ItemPuller`] from the concurrent iterator.
771    /// The created item puller can be used to pull elements one by one from the
772    /// data source.
773    ///
774    /// Note that `ItemPuller` implements a regular [`Iterator`].
775    /// This not only enables the `for` loops but also makes all iterator methods available.
776    /// For instance, we can use `filter`, `map` and/or `reduce` on the item puller iterator
777    /// as we do with regular iterators, while under the hood it will concurrently iterate
778    /// over the elements of the concurrent iterator.
779    ///
780    /// Alternatively, [`item_puller_with_idx`] can be used to create an iterator
781    /// which also yields the indices of the items.
782    ///
783    /// [`item_puller`]: crate::ConcurrentIter::item_puller
784    /// [`item_puller_with_idx`]: crate::ConcurrentIter::item_puller_with_idx
785    ///
786    /// # Examples
787    ///
788    /// ## Concurrent looping with `for`
789    ///
790    /// In the following program, we use a regular `for` loop over the item pullers, one created
791    /// created for each thread. All item pullers being created from the same concurrent iterator
792    /// will actually concurrently pull items from the same data source.
793    ///
794    /// ```
795    /// use orx_concurrent_iter::*;
796    ///
797    /// let num_threads = 4;
798    /// let data: Vec<_> = (0..100).map(|x| x.to_string()).collect();
799    /// let con_iter = data.con_iter();
800    ///
801    /// let process = |_x: &String| { /* assume actual work */ };
802    ///
803    /// std::thread::scope(|s| {
804    ///     for _ in 0..num_threads {
805    ///         s.spawn(|| {
806    ///             // concurrently iterate over values in a `for` loop
807    ///             for value in con_iter.item_puller() {
808    ///                 process(value);
809    ///             }
810    ///         });
811    ///     }
812    /// });
813    /// ```
814    ///
815    /// ## Parallel reduce
816    ///
817    /// As mentioned above, item puller makes all convenient Iterator methods available in a concurrent
818    /// program. The following simple program demonstrate a very convenient way to implement a parallel
819    /// reduce operation.
820    ///
821    /// ```
822    /// use orx_concurrent_iter::*;
823    ///
824    /// fn parallel_reduce<T, F>(
825    ///     num_threads: usize,
826    ///     con_iter: impl ConcurrentIter<Item = T>,
827    ///     reduce: F,
828    /// ) -> Option<T>
829    /// where
830    ///     T: Send,
831    ///     F: Fn(T, T) -> T + Sync,
832    /// {
833    ///     std::thread::scope(|s| {
834    ///         (0..num_threads)
835    ///             .map(|_| s.spawn(|| con_iter.item_puller().reduce(&reduce))) // reduce inside each thread
836    ///             .filter_map(|x| x.join().unwrap()) // join threads, ignore None's
837    ///             .reduce(&reduce) // reduce thread results to final result
838    ///     })
839    /// }
840    ///
841    /// // test
842    ///
843    /// let sum = parallel_reduce(8, (0..0).into_con_iter(), |a, b| a + b);
844    /// assert_eq!(sum, None);
845    ///
846    /// let sum = parallel_reduce(8, (0..3).into_con_iter(), |a, b| a + b);
847    /// assert_eq!(sum, Some(3));
848    ///
849    /// let n = 10_000;
850    /// let data: Vec<_> = (0..n).collect();
851    /// let sum = parallel_reduce(8, data.con_iter().copied(), |a, b| a + b);
852    /// assert_eq!(sum, Some(n * (n - 1) / 2));
853    /// ```
854    fn item_puller(&self) -> ItemPuller<'_, Self>
855    where
856        Self: Sized,
857    {
858        self.into()
859    }
860
861    /// Creates a [`EnumeratedItemPuller`] from the concurrent iterator.
862    /// The created item puller can be used to `pull` elements one by one from the
863    /// data source together with the index of the elements.
864    ///
865    /// Note that `EnumeratedItemPuller` implements a regular [`Iterator`].
866    /// This not only enables the `for` loops but also makes all iterator methods available.
867    /// For instance, we can use `filter`, `map` and/or `reduce` on the item puller iterator
868    /// as we do with regular iterators, while under the hood it will concurrently iterate
869    /// over the elements of the concurrent iterator.
870    ///
871    /// See also [`enumerate`] to convert the concurrent iterator into its enumerated
872    /// counterpart.
873    ///
874    /// [`EnumeratedItemPuller`]: crate::EnumeratedItemPuller
875    /// [`enumerate`]: crate::ConcurrentIter::enumerate
876    ///
877    /// # Examples
878    ///
879    /// ```
880    /// use orx_concurrent_iter::*;
881    ///
882    /// let num_threads = 4;
883    /// let data: Vec<_> = (0..100).map(|x| x.to_string()).collect();
884    /// let con_iter = data.con_iter();
885    ///
886    /// let process = |_idx: usize, _x: &String| { /* assume actual work */ };
887    ///
888    /// std::thread::scope(|s| {
889    ///     for _ in 0..num_threads {
890    ///         s.spawn(|| {
891    ///             // concurrently iterate over values in a `for` loop
892    ///             for (idx, value) in con_iter.item_puller_with_idx() {
893    ///                 process(idx, value);
894    ///             }
895    ///         });
896    ///     }
897    /// });
898    /// ```
899    fn item_puller_with_idx(&self) -> EnumeratedItemPuller<'_, Self>
900    where
901        Self: Sized,
902    {
903        self.into()
904    }
905
906    // provided transformations
907
908    /// Creates an iterator which copies all of its elements.
909    ///
910    /// This is useful when you have an iterator over `&T`, but you need an iterator over `T`.
911    ///
912    /// # Examples
913    ///
914    /// ```
915    /// use orx_concurrent_iter::*;
916    ///
917    /// let vec = vec!['x', 'y'];
918    ///
919    /// let con_iter = vec.con_iter();
920    /// assert_eq!(con_iter.next(), Some(&'x'));
921    /// assert_eq!(con_iter.next(), Some(&'y'));
922    /// assert_eq!(con_iter.next(), None);
923    ///
924    /// let con_iter = vec.con_iter().copied();
925    /// assert_eq!(con_iter.next(), Some('x'));
926    /// assert_eq!(con_iter.next(), Some('y'));
927    /// assert_eq!(con_iter.next(), None);
928    /// ```
929    fn copied<'a, T>(self) -> ConIterCopied<'a, Self, T>
930    where
931        T: Copy,
932        Self: ConcurrentIter<Item = &'a T> + Sized,
933    {
934        ConIterCopied::new(self)
935    }
936
937    /// Creates an iterator which clones all of its elements.
938    ///
939    /// This is useful when you have an iterator over `&T`, but you need an iterator over `T`.
940    ///
941    /// # Examples
942    ///
943    /// ```
944    /// use orx_concurrent_iter::*;
945    ///
946    /// let vec = vec![String::from("x"), String::from("y")];
947    ///
948    /// let con_iter = vec.con_iter();
949    /// assert_eq!(con_iter.next(), Some(&String::from("x")));
950    /// assert_eq!(con_iter.next(), Some(&String::from("y")));
951    /// assert_eq!(con_iter.next(), None);
952    ///
953    /// let con_iter = vec.con_iter().cloned();
954    /// assert_eq!(con_iter.next(), Some(String::from("x")));
955    /// assert_eq!(con_iter.next(), Some(String::from("y")));
956    /// assert_eq!(con_iter.next(), None);
957    /// ```
958    fn cloned<'a, T>(self) -> ConIterCloned<'a, Self, T>
959    where
960        T: Clone,
961        Self: ConcurrentIter<Item = &'a T> + Sized,
962    {
963        ConIterCloned::new(self)
964    }
965
966    /// Creates an iterator which gives the current iteration count as well as the next value.
967    ///
968    /// The iterator returned yields pairs `(i, val)`, where `i` is the current index of iteration
969    /// and `val` is the value returned by the iterator.
970    ///
971    /// Note that concurrent iterators are already capable of returning hte element index by methods
972    /// such as:
973    ///
974    /// * [`next_with_idx`]
975    /// * [`item_puller_with_idx`]
976    /// * or [`pull_with_idx`] method of the chunk puller created by [`chunk_puller`]
977    ///
978    /// However, when we want always need the index, it is convenient to convert the concurrent iterator
979    /// into its enumerated counterpart with this method.
980    ///
981    /// [`next_with_idx`]: crate::ConcurrentIter::next_with_idx
982    /// [`item_puller_with_idx`]: crate::ConcurrentIter::item_puller_with_idx
983    /// [`chunk_puller`]: crate::ConcurrentIter::chunk_puller
984    /// [`pull_with_idx`]: crate::ChunkPuller::pull_with_idx
985    ///
986    /// # Examples
987    ///
988    /// ```
989    /// use orx_concurrent_iter::*;
990    ///
991    /// let vec = vec!['x', 'y'];
992    ///
993    /// let con_iter = vec.con_iter().enumerate();
994    /// assert_eq!(con_iter.next(), Some((0, &'x')));
995    /// assert_eq!(con_iter.next(), Some((1, &'y')));
996    /// assert_eq!(con_iter.next(), None);
997    /// ```
998    fn enumerate(self) -> Enumerate<Self>
999    where
1000        Self: Sized,
1001    {
1002        Enumerate::new(self)
1003    }
1004
1005    /// Creates a chain of this and `other` concurrent iterators.
1006    ///
1007    /// It is preferable to call [`chain`] over `chain_inexact` whenever the first iterator
1008    /// implements `ExactSizeConcurrentIter`.
1009    ///
1010    /// [`chain`]: crate::ExactSizeConcurrentIter::chain
1011    ///
1012    /// # Examples
1013    ///
1014    /// ```
1015    /// use orx_concurrent_iter::*;
1016    ///
1017    /// let s1 = "abcxyz".chars().filter(|x| !['x', 'y', 'z'].contains(x)); // inexact iter
1018    /// let s2 = vec!['d', 'e', 'f'];
1019    ///
1020    /// let chain = s1.iter_into_con_iter().chain_inexact(s2);
1021    ///
1022    /// assert_eq!(chain.next(), Some('a'));
1023    /// assert_eq!(chain.next(), Some('b'));
1024    /// assert_eq!(chain.next(), Some('c'));
1025    /// assert_eq!(chain.next(), Some('d'));
1026    /// assert_eq!(chain.next(), Some('e'));
1027    /// assert_eq!(chain.next(), Some('f'));
1028    /// assert_eq!(chain.next(), None);
1029    /// ```
1030    fn chain_inexact<C>(self, other: C) -> ChainUnknownLenI<Self, C::IntoIter>
1031    where
1032        C: IntoConcurrentIter<Item = Self::Item>,
1033        Self: Sized,
1034    {
1035        ChainUnknownLenI::new(self, other.into_con_iter())
1036    }
1037}