Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make @threads :dynamic default #44136

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ Command-line option changes
Multi-threading changes
-----------------------

* A new `:dynamic` schedule option for `Threads.@threads` which is similar to the default behavior except iterations
will be scheduled dynamically to available worker threads rather than pinned to each thread. This option is more
composable with (possibly nested) `@spawn` and `@threads` loops ([#43919])
* `Threads.@threads` now defaults to a new `:dynamic` schedule option which is similar to the previous behavior except
that iterations will be scheduled dynamically to available worker threads rather than pinned to each thread. This
behavior is more composable with (possibly nested) `@spawn` and `@threads` loops ([#43919], [#44136])

Build system changes
--------------------
Expand Down
33 changes: 11 additions & 22 deletions base/threadingconstructs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,11 @@ function _threadsfor(iter, lbody, schedule)
end
end
end
if $(schedule === :dynamic)
if $(schedule === :dynamic || schedule === :default)
threading_run(threadsfor_fun, false)
elseif ccall(:jl_in_threaded_region, Cint, ()) != 0
$(if schedule === :static
:(error("`@threads :static` cannot be used concurrently or nested"))
else
# only use threads when called from outside @threads
:(threadsfor_fun(onethread = true))
end)
else
elseif ccall(:jl_in_threaded_region, Cint, ()) != 0 # :static
error("`@threads :static` cannot be used concurrently or nested")
else # :static
threading_run(threadsfor_fun, true)
end
nothing
Expand Down Expand Up @@ -126,21 +121,21 @@ For example, the above conditions imply that:
- Communicating between iterations using blocking primitives like `Channel`s is incorrect.
- Write only to locations not shared across iterations (unless a lock or atomic operation is used).


Schedule options are:
- `:static` creates one task per thread and divides the iterations equally among
them, assigning each task specifically to each thread.
Specifying `:static` is an error if used from inside another `@threads` loop
or from a thread other than 1.
- `:dynamic` will schedule iterations dynamically to available worker threads,
- `:dynamic` (default) will schedule iterations dynamically to available worker threads,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps the default should be listed first

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense #44163

assuming that the workload for each iteration is uniform.

Without the scheduler argument, the exact scheduling is unspecified; i.e. it may be
different across Julia releases. Currently, the behavior is dependent on the calling thread.
The default is `:static` when called from thread 1. The loop will be executed without threading
when called from other threads.
Without the scheduler argument, the exact scheduling is unspecified and varies across Julia releases.

The default schedule (used when no `schedule` argument is present) is subject to change.
!!! compat "Julia 1.5"
The `schedule` argument is available as of Julia 1.5.

!!! compat "Julia 1.8"
The `:dynamic` option for the `schedule` argument is available and the default as of Julia 1.8.

For example, an illustration of the different scheduling strategies where `busywait`
is a non-yielding timed loop that runs for a number of seconds.
Expand Down Expand Up @@ -172,12 +167,6 @@ julia> @time begin
The `:dynamic` example takes 2 seconds since one of the non-occupied threads is able
to run two of the 1-second iterations to complete the for loop.

!!! compat "Julia 1.5"
The `schedule` argument is available as of Julia 1.5.

!!! compat "Julia 1.8"
The `:dynamic` option for the `schedule` argument is available as of Julia 1.8.

See also: [`@spawn`](@ref Threads.@spawn), [`nthreads()`](@ref Threads.nthreads),
[`threadid()`](@ref Threads.threadid), `pmap` in [`Distributed`](@ref man-distributed),
`BLAS.set_num_threads` in [`LinearAlgebra`](@ref man-linalg).
Expand Down
6 changes: 3 additions & 3 deletions test/threads_exec.jl
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ function test_thread_cfunction()
@test cfs[2] == cf(fs[2])
@test length(unique(cfs)) == 1000
ok = zeros(Int, nthreads())
@threads for i in 1:10000
@threads :static for i in 1:10000
i = mod1(i, 1000)
fi = fs[i]
cfi = cf(fi)
Expand Down Expand Up @@ -705,9 +705,9 @@ let ch = Channel{Char}(0), t
@test String(collect(ch)) == "hello"
end

# errors inside @threads
# errors inside @threads :static
function _atthreads_with_error(a, err)
Threads.@threads for i in eachindex(a)
Threads.@threads :static for i in eachindex(a)
if err
error("failed")
end
Expand Down