Function maybe_rayon::join
pub fn join<A, B, RA, RB>(oper_a: A, oper_b: B) -> (RA, RB)where
A: FnOnce() -> RA + Send,
B: FnOnce() -> RB + Send,
RA: Send,
RB: Send,
Expand description
Takes two closures and potentially runs them in parallel. It returns a pair of the results from those closures.
Conceptually, calling join()
is similar to spawning two threads,
one executing each of the two closures. However, the
implementation is quite different and incurs very low
overhead. The underlying technique is called “work stealing”: the
Rayon runtime uses a fixed pool of worker threads and attempts to
only execute code in parallel when there are idle CPUs to handle
it.
When join
is called from outside the thread pool, the calling
thread will block while the closures execute in the pool. When
join
is called within the pool, the calling thread still actively
participates in the thread pool. It will begin by executing closure
A (on the current thread). While it is doing that, it will advertise
closure B as being available for other threads to execute. Once closure A
has completed, the current thread will try to execute closure B;
if however closure B has been stolen, then it will look for other work
while waiting for the thief to fully execute closure B. (This is the
typical work-stealing strategy).
Examples
This example uses join to perform a quick-sort (note this is not a
particularly optimized implementation: if you actually want to
sort for real, you should prefer the par_sort
method offered
by Rayon).
let mut v = vec![5, 1, 8, 22, 0, 44];
quick_sort(&mut v);
assert_eq!(v, vec![0, 1, 5, 8, 22, 44]);
fn quick_sort<T:PartialOrd+Send>(v: &mut [T]) {
if v.len() > 1 {
let mid = partition(v);
let (lo, hi) = v.split_at_mut(mid);
rayon::join(|| quick_sort(lo),
|| quick_sort(hi));
}
}
// Partition rearranges all items `<=` to the pivot
// item (arbitrary selected to be the last item in the slice)
// to the first half of the slice. It then returns the
// "dividing point" where the pivot is placed.
fn partition<T:PartialOrd+Send>(v: &mut [T]) -> usize {
let pivot = v.len() - 1;
let mut i = 0;
for j in 0..pivot {
if v[j] <= v[pivot] {
v.swap(i, j);
i += 1;
}
}
v.swap(i, pivot);
i
}
Warning about blocking I/O
The assumption is that the closures given to join()
are
CPU-bound tasks that do not perform I/O or other blocking
operations. If you do perform I/O, and that I/O should block
(e.g., waiting for a network request), the overall performance may
be poor. Moreover, if you cause one closure to be blocked waiting
on another (for example, using a channel), that could lead to a
deadlock.
Panics
No matter what happens, both closures will always be executed. If
a single closure panics, whether it be the first or second
closure, that panic will be propagated and hence join()
will
panic with the same panic value. If both closures panic, join()
will panic with the panic value from the first closure.