Skip to content

Commit 2f67b51

Browse files
tkfIanButterworth
andauthoredMar 3, 2022
Clarify the behavior of @threads for (#44168)
* Clarify the behavior of `@threads for` Co-authored-by: Ian Butterworth <[email protected]>
1 parent 82dc130 commit 2f67b51

File tree

1 file changed

+62
-30
lines changed

1 file changed

+62
-30
lines changed
 

‎base/threadingconstructs.jl

Lines changed: 62 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -99,46 +99,82 @@ end
9999
"""
100100
Threads.@threads [schedule] for ... end
101101
102-
A macro to parallelize a `for` loop to run with multiple threads. Splits the iteration
103-
space among multiple tasks and runs those tasks on threads according to a scheduling
104-
policy.
105-
A barrier is placed at the end of the loop which waits for all tasks to finish
106-
execution.
107-
108-
The `schedule` argument can be used to request a particular scheduling policy.
109-
110-
Except for `:static` scheduling, how the iterations are assigned to tasks, and how the tasks
111-
are assigned to the worker threads is undefined. The exact assignments can be different
112-
for each execution. The scheduling option is a hint. The loop body code (including any code
113-
transitively called from it) must not make assumptions about the distribution of iterations
114-
to tasks or the worker thread in which they are executed. The loop body for each iteration
115-
must be able to make forward progress independent of other iterations and be free from data
116-
races. As such, synchronizations across iterations may deadlock.
102+
A macro to execute a `for` loop in parallel. The iteration space is distributed to
103+
coarse-grained tasks. This policy can be specified by the `schedule` argument. The
104+
execution of the loop waits for the evaluation of all iterations.
105+
106+
See also: [`@spawn`](@ref Threads.@spawn) and
107+
`pmap` in [`Distributed`](@ref man-distributed).
108+
109+
# Extended help
110+
111+
## Semantics
112+
113+
Unless stronger guarantees are specified by the scheduling option, the loop executed by
114+
`@threads` macro have the following semantics.
115+
116+
The `@threads` macro executes the loop body in an unspecified order and potentially
117+
concurrently. It does not specify the exact assignments of the tasks and the worker threads.
118+
The assignments can be different for each execution. The loop body code (including any code
119+
transitively called from it) must not make any assumptions about the distribution of
120+
iterations to tasks or the worker thread in which they are executed. The loop body for each
121+
iteration must be able to make forward progress independent of other iterations and be free
122+
from data races. As such, invalid synchronizations across iterations may deadlock while
123+
unsynchronized memory accesses may result in undefined behavior.
117124
118125
For example, the above conditions imply that:
119126
120127
- The lock taken in an iteration *must* be released within the same iteration.
121128
- Communicating between iterations using blocking primitives like `Channel`s is incorrect.
122-
- Write only to locations not shared across iterations (unless a lock or atomic operation is used).
129+
- Write only to locations not shared across iterations (unless a lock or atomic operation is
130+
used).
131+
- The value of [`threadid()`](@ref Threads.threadid) may change even within a single
132+
iteration.
123133
124-
Schedule options are:
125-
- `:dynamic` (default) will schedule iterations dynamically to available worker threads,
126-
assuming that the workload for each iteration is uniform.
127-
- `:static` creates one task per thread and divides the iterations equally among
128-
them, assigning each task specifically to each thread.
129-
Specifying `:static` is an error if used from inside another `@threads` loop
130-
or from a thread other than 1.
134+
## Schedulers
131135
132-
Without the scheduler argument, the exact scheduling is unspecified and varies across Julia releases.
136+
Without the scheduler argument, the exact scheduling is unspecified and varies across Julia
137+
releases. Currently, `:dynamic` is used when the scheduler is not specified.
133138
134139
!!! compat "Julia 1.5"
135140
The `schedule` argument is available as of Julia 1.5.
136141
142+
### `:dynamic` (default)
143+
144+
`:dynamic` scheduler executes iterations dynamically to available worker threads. Current
145+
implementation assumes that the workload for each iteration is uniform. However, this
146+
assumption may be removed in the future.
147+
148+
This scheduling option is merely a hint to the underlying execution mechanism. However, a
149+
few properties can be expected. The number of `Task`s used by `:dynamic` scheduler is
150+
bounded by a small constant multiple of the number of available worker threads
151+
([`nthreads()`](@ref Threads.nthreads)). Each task processes contiguous regions of the
152+
iteration space. Thus, `@threads :dynamic for x in xs; f(x); end` is typically more
153+
efficient than `@sync for x in xs; @spawn f(x); end` if `length(xs)` is significantly
154+
larger than the number of the worker threads and the run-time of `f(x)` is relatively
155+
smaller than the cost of spawning and synchronizaing a task (typically less than 10
156+
microseconds).
157+
137158
!!! compat "Julia 1.8"
138159
The `:dynamic` option for the `schedule` argument is available and the default as of Julia 1.8.
139160
140-
For example, an illustration of the different scheduling strategies where `busywait`
141-
is a non-yielding timed loop that runs for a number of seconds.
161+
### `:static`
162+
163+
`:static` scheduler creates one task per thread and divides the iterations equally among
164+
them, assigning each task specifically to each thread. In particular, the value of
165+
[`threadid()`](@ref Threads.threadid) is guranteed to be constant within one iteration.
166+
Specifying `:static` is an error if used from inside another `@threads` loop or from a
167+
thread other than 1.
168+
169+
!!! note
170+
`:static` scheduling exists for supporting transition of code written before Julia 1.3.
171+
In newly written library functions, `:static` scheduling is discouraged because the
172+
functions using this option cannot be called from arbitrary worker threads.
173+
174+
## Example
175+
176+
To illustrate of the different scheduling strategies, consider the following function
177+
`busywait` containing a non-yielding timed loop that runs for a given number of seconds.
142178
143179
```julia-repl
144180
julia> function busywait(seconds)
@@ -166,10 +202,6 @@ julia> @time begin
166202
167203
The `:dynamic` example takes 2 seconds since one of the non-occupied threads is able
168204
to run two of the 1-second iterations to complete the for loop.
169-
170-
See also: [`@spawn`](@ref Threads.@spawn), [`nthreads()`](@ref Threads.nthreads),
171-
[`threadid()`](@ref Threads.threadid), `pmap` in [`Distributed`](@ref man-distributed),
172-
`BLAS.set_num_threads` in [`LinearAlgebra`](@ref man-linalg).
173205
"""
174206
macro threads(args...)
175207
na = length(args)

0 commit comments

Comments
 (0)
Please sign in to comment.