-
Notifications
You must be signed in to change notification settings - Fork 7
TaskParallelism
The package parallel
provides functions for expressing task-based parallel algorithms, by spawning each task in its own goroutine, and managing task synchronization and panics implicitly to make the semantics similar to a sequential execution of the same program. (Parallelism is not concurrency. Unlike in concurrent programming, mimicking sequential semantics is actually desirable to a certain extent when using parallelism to speed up otherwise sequential algorithms.)
Some of the functions are also available as speculative variants in the speculative
package. The implementations in the parallel
package always wait for all spawned tasks to terminate, whereas the implementations in the speculative
package terminate earlier if they can.
While functions in the speculative
package may terminate early, they do not proactively stop the execution of the spawned goroutines. To ensure that compute resources are freed up in such cases, user programs need to use some other safe form of communication to gracefully stop their execution, for example by using the cancelation feature of Go's context package. (Any such additional communication is likely to add additional performance overhead, which may not be desirable and which is why this is not done by default.)
All functions are also available as purely sequential implementations in the sequential
package, which are useful for testing and debugging. (It is not recommended to use the implementations of the sequential
package for any other purpose, because they are almost certainly too inefficient for regular sequential programs.)
Function | Parameters | Results | also speculative? |
---|---|---|---|
Do |
...Thunk |
--- | no |
ErrDo |
...ErrThunk |
error |
yes |
And |
...Predicate |
bool |
yes |
ErrAnd |
...ErrPredicate |
bool, error |
yes |
Or |
...Predicate |
bool |
yes |
ErrOr |
...ErrPredicate |
bool, error |
yes |
Do
, And
, and Or
are useful for executing two or more tasks in parallel, by invoking them each in their own goroutine. Do
does this for tasks implemented as functions without return values. And
and Or
do this for predicates that return boolean values, and combine these return values with the && or || operator.
ErrDo
, ErrAnd
, and ErrOr
are like Do
, And
, and Or
, except that the spawned tasks can additionally also return error values. If any task returns an error value different from nil
, the left-most of those non-nil
error value is returned by ErrDo
, ErrAnd
, or ErrOr
(in addition to the primary return value in the case of ErrAnd
and ErrOr
).
parallel.Do
, parallel.ErrDo
, parallel.And
, parallel.ErrAnd
, parallel.Or
, and parallel.ErrOr
wait for all spawned tasks to terminate before returning.
The speculative
package additionally provides the following variants:
-
speculative.ErrDo
terminates early if any of the spawned tasks returns an error value different fromnil
. -
speculative.And
andspeculative.Or
terminate early if the final return value is known early (if any of the spawned predicates returnsfalse
forAnd
, ortrue
forOr
). -
speculative.ErrAnd
andspeculative.ErrOr
terminate early both if the final return value is known early, or if any predicate returns a non-nil
error value.
The Range
-functions described below are useful for expressing parallel algorithms over slices, or other domains that can be covered by a range of consecutive integers. They all receive a range and a single range function, divide the range up into subranges, and spawn the range function for each of these subranges. Some of the functions described below also combine return values of the subrange tasks into an overall result, and do so in parallel.
Function | Parameters | Results | also speculative? |
---|---|---|---|
Range |
low, high, n int, f RangeFunc
|
none | no |
ErrRange |
low, high, n int, f ErrRangeFunc
|
error |
yes |
RangeAnd |
low, high, n int, f RangePredicate
|
bool |
yes |
ErrRangeAnd |
low, high, n int, f ErrRangePredicate
|
bool, error |
yes |
RangeOr |
low, high, n int, f RangePredicate
|
bool |
yes |
ErrRangeOr |
low, high, n int, f ErrRangePredicate
|
bool, error |
yes |
RangeReduce |
low, high, n int, reduce RangeReducer , pair PairReducer
|
interface{} |
no |
ErrRangeReduce |
low, high, n int, reduce ErrRangeReducer , pair ErrPairReducer
|
interface{}, error |
yes |
IntRangeReduce |
low, high, n int, reduce IntRangeReducer , pair IntPairReducer
|
int |
no |
ErrIntRangeReduce |
low, high, n int, reduce ErrIntRangeReducer , pair ErrIntPairReducer
|
int, error |
yes |
Float64RangeReduce |
low, high, n int, reduce Float64RangeReducer , pair Float64PairReducer
|
float64 |
no |
ErrFloat64RangeReduce |
low, high, n int, reduce ErrFloat64RangeReducer , pair ErrFloat64RangeReducer
|
float64, error |
yes |
StringRangeReduce |
low, high, n int, reduce StringRangeReducer , pair StringPairReducer
|
string |
no |
ErrStringRangeReduce |
low, high, n int, reduce ErrStringRangeReducer , pair ErrStringPairReducer
|
string, error |
yes |
The range is always specified by a low
and high
integer, with low <= high
, and the functions will cover the half-open interval ranging from low
to high
, including low
but excluding high
.
The n
parameter can typically be set to 0. It is used to determine how the size of the overall range high - low
is divided up over the available worker threads. If n
is 0, a reasonable default is chosen that typically works well. If n
is greater than 0, the size of the overall range high - low
is divided by n
. (This means that n
tasks are spawned. Quite often, n
should be larger than the number of available worker threads to improve load balancing. Passing 0 for n
ensures that this is taken into account.)
The subrange functions that are passed by user programs as parameters receive a low
and high
integer specifying the respective subrange, with low <= high
. They should in turn cover the respective half-open interval ranging from low
to high
, including low
but excluding high
, and usually do so sequentially with a simple for
loop.
Range
, RangeAnd
, and RangeOr
are similar to Do
, And
, and Or
, except they receive a range and a single range function each. They divide the range up into subranges and spawn the range function for each of these subranges. RangeAnd
and RangeOr
additionally combine the boolean return values of the range predicates with the &&
or ||
operator.
RangeReduce
is similar to RangeAnd
and RangeOr
, except that the results are of type interface{}
instead of bool
, and the results are combined using another function that is explicitly passed as a parameter. IntRangeReduce
, Float64RangeReduce
, and StringRangeReduce
can be used in case it is known that the result types are int
, float64
, or string
respectively.
ErrRange
, ErrRangeAnd
, ErrRangeOr
, ErrRangeReduce
, ErrIntRangeReduce
, ErrFloat64RangeReduce
, and ErrStringRangeReduce
behave like the corresponding non-Err
-variants above, except that the subrange functions can additionally also return error values. If any subrange function returns an error value different from nil, the left-most of those non-nil
error value is returned.
parallel.Range
, parallel.RangeAnd
, parallel.RangeOr
, parallel.RangeReduce
, parallel.IntRangeReduce
, parallel.Float64RangeReduce
, parallel.StringRangeReduce
, and their corresponding Err
-variants, wait for all spawned tasks to terminate before returning.
The speculative
package additionally provides the following variants:
-
speculative.RangeAnd
andspeculative.RangeOr
terminate early if the final return value is known early (if any of the spawned predicates returnsfalse
forRangeAnd
, ortrue
forRangeOr
). -
speculative.ErrRange
,speculative.ErrRangeReduce
,speculative.ErrIntRangeReduce
,speculative.ErrFloat64RangeReduce
, andspeculative.ErrStringRangeReduce
terminate early if any of the spawned tasks returns an error value different fromnil
. -
speculative.ErrRangeAnd
andspeculative.ErrRangeOr
terminate early both if the final return value is known early, or if any of the spawned tasks return a non-nil
error value.
All of the functions from the parallel
package described above also take care of dealing with panics: If one or more of the tasks spawned by a parallel
function panics, the panic is recovered in the corresponding goroutine, and the parallel
function then panics with the left-most of the recovered panic values.
The handling of panics is done left-to-right to ensure that the behavior is semantically similar to a corresponding sequential left-to-right execution, just as is the case with error values.
Hoever, functions in the speculative
package may not propagate panics from one task in case they terminate early because of a known return value or a non-nil
error value originating from a different task. See the documentations of the speculative
functions for more precise details of the semantics.