|
99 | 99 | """
|
100 | 100 | Threads.@threads [schedule] for ... end
|
101 | 101 |
|
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. |
117 | 124 |
|
118 | 125 | For example, the above conditions imply that:
|
119 | 126 |
|
120 | 127 | - The lock taken in an iteration *must* be released within the same iteration.
|
121 | 128 | - 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. |
123 | 133 |
|
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 |
131 | 135 |
|
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. |
133 | 138 |
|
134 | 139 | !!! compat "Julia 1.5"
|
135 | 140 | The `schedule` argument is available as of Julia 1.5.
|
136 | 141 |
|
| 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 | +
|
137 | 158 | !!! compat "Julia 1.8"
|
138 | 159 | The `:dynamic` option for the `schedule` argument is available and the default as of Julia 1.8.
|
139 | 160 |
|
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. |
142 | 178 |
|
143 | 179 | ```julia-repl
|
144 | 180 | julia> function busywait(seconds)
|
@@ -166,10 +202,6 @@ julia> @time begin
|
166 | 202 |
|
167 | 203 | The `:dynamic` example takes 2 seconds since one of the non-occupied threads is able
|
168 | 204 | 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). |
173 | 205 | """
|
174 | 206 | macro threads(args...)
|
175 | 207 | na = length(args)
|
|
0 commit comments