From b6631a70245f75c2061f9bdb4f34963032b96ab8 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Sun, 13 Feb 2022 01:34:59 -0500 Subject: [PATCH] Make `@threads :dynamic` default (#44136) Co-authored-by: Takafumi Arakaki --- NEWS.md | 6 +++--- base/threadingconstructs.jl | 33 +++++++++++---------------------- test/threads_exec.jl | 6 +++--- 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/NEWS.md b/NEWS.md index a1167283d0b63e..fd217eb7f954f0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 -------------------- diff --git a/base/threadingconstructs.jl b/base/threadingconstructs.jl index 123fa62b120828..74234c48bf2427 100644 --- a/base/threadingconstructs.jl +++ b/base/threadingconstructs.jl @@ -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 @@ -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, 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. @@ -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). diff --git a/test/threads_exec.jl b/test/threads_exec.jl index 7b03f48f5eec6d..1b146f48e8c572 100644 --- a/test/threads_exec.jl +++ b/test/threads_exec.jl @@ -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) @@ -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