From 6f174d1943b63fe3667ecc06708c65fb56e9addc Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Fri, 17 Jan 2025 15:24:59 -0500 Subject: [PATCH 1/5] default to 1 interactive thread --- src/jloptions.c | 20 ++++++++++---------- src/threading.c | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/jloptions.c b/src/jloptions.c index 2c5a9074eb465..7a504992ad02b 100644 --- a/src/jloptions.c +++ b/src/jloptions.c @@ -623,8 +623,10 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) break; case 't': // threads errno = 0; - jl_options.nthreadpools = 1; - long nthreads = -1, nthreadsi = 0; + jl_options.nthreadpools = 2; + // By default, main threads = -1 (== "auto"), interactive = 1 + long nthreads = -1, nthreadsi = 1; + if (!strncmp(optarg, "auto", 4)) { jl_options.nthreads = -1; if (optarg[4] == ',') { @@ -633,10 +635,9 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) else { errno = 0; nthreadsi = strtol(&optarg[5], &endptr, 10); - if (errno != 0 || endptr == &optarg[5] || *endptr != 0 || nthreadsi < 1 || nthreadsi >= INT16_MAX) - jl_errorf("julia: -t,--threads=auto,; m must be an integer >= 1"); + if (errno != 0 || endptr == &optarg[5] || *endptr != 0 || nthreadsi < 0 || nthreadsi >= INT16_MAX) + jl_errorf("julia: -t,--threads=auto,; m must be an integer >= 0"); } - jl_options.nthreadpools++; } } else { @@ -650,17 +651,16 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) errno = 0; char *endptri; nthreadsi = strtol(&endptr[1], &endptri, 10); - if (errno != 0 || endptri == &endptr[1] || *endptri != 0 || nthreadsi < 1 || nthreadsi >= INT16_MAX) - jl_errorf("julia: -t,--threads=,; n and m must be integers >= 1"); + // Allow 0 for interactive + if (errno != 0 || endptri == &endptr[1] || *endptri != 0 || nthreadsi < 0 || nthreadsi >= INT16_MAX) + jl_errorf("julia: -t,--threads=,; m must be an integer ≥ 0"); } - jl_options.nthreadpools++; } jl_options.nthreads = nthreads + nthreadsi; } int16_t *ntpp = (int16_t *)malloc_s(jl_options.nthreadpools * sizeof(int16_t)); ntpp[0] = (int16_t)nthreads; - if (jl_options.nthreadpools == 2) - ntpp[1] = (int16_t)nthreadsi; + ntpp[1] = (int16_t)nthreadsi; jl_options.nthreads_per_pool = ntpp; break; case 'p': // procs diff --git a/src/threading.c b/src/threading.c index 77956786af3f4..f5c84d862f660 100644 --- a/src/threading.c +++ b/src/threading.c @@ -698,7 +698,7 @@ void jl_init_threading(void) // and `jl_n_threads_per_pool`. jl_n_threadpools = 2; int16_t nthreads = JULIA_NUM_THREADS; - int16_t nthreadsi = 0; + int16_t nthreadsi = 1; char *endptr, *endptri; if (jl_options.nthreads != 0) { // --threads specified From 495d9f85e15723265145ab2da078cab1631e566b Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Fri, 17 Jan 2025 16:32:58 -0500 Subject: [PATCH 2/5] default to 0 if generating output --- src/jloptions.c | 7 +++++-- src/threading.c | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/jloptions.c b/src/jloptions.c index 7a504992ad02b..0da6108814b35 100644 --- a/src/jloptions.c +++ b/src/jloptions.c @@ -624,8 +624,11 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) case 't': // threads errno = 0; jl_options.nthreadpools = 2; - // By default, main threads = -1 (== "auto"), interactive = 1 - long nthreads = -1, nthreadsi = 1; + // By default: + // default threads = -1 (== "auto") + long nthreads = -1; + // interactive threads = 1, or 0 if generating output + long nthreadsi = jl_generating_output() ? 0 : 1; if (!strncmp(optarg, "auto", 4)) { jl_options.nthreads = -1; diff --git a/src/threading.c b/src/threading.c index f5c84d862f660..eddff1e0b78f6 100644 --- a/src/threading.c +++ b/src/threading.c @@ -698,7 +698,8 @@ void jl_init_threading(void) // and `jl_n_threads_per_pool`. jl_n_threadpools = 2; int16_t nthreads = JULIA_NUM_THREADS; - int16_t nthreadsi = 1; + // if generating output default to 0 interactive threads, otherwise default to 1 + int16_t nthreadsi = jl_generating_output() ? 0 : 1; char *endptr, *endptri; if (jl_options.nthreads != 0) { // --threads specified From 24a1fd9e6873b4a972d7726eaccb1c222b72e9fe Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Sat, 18 Jan 2025 10:14:37 -0500 Subject: [PATCH 3/5] un-doctest examples that aren't guaranteed threaded output order --- doc/src/manual/faq.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/src/manual/faq.md b/doc/src/manual/faq.md index 2673ca7532acf..178a674e46643 100644 --- a/doc/src/manual/faq.md +++ b/doc/src/manual/faq.md @@ -941,7 +941,7 @@ While the streaming I/O API is synchronous, the underlying implementation is ful Consider the printed output from the following: -```jldoctest +``` julia> @sync for i in 1:3 Threads.@spawn write(stdout, string(i), " Foo ", " Bar ") end @@ -954,7 +954,7 @@ yields to other tasks while waiting for that part of the I/O to complete. `print` and `println` "lock" the stream during a call. Consequently changing `write` to `println` in the above example results in: -```jldoctest +``` julia> @sync for i in 1:3 Threads.@spawn println(stdout, string(i), " Foo ", " Bar ") end @@ -965,7 +965,7 @@ julia> @sync for i in 1:3 You can lock your writes with a `ReentrantLock` like this: -```jldoctest +``` julia> l = ReentrantLock(); julia> @sync for i in 1:3 From ac4361bbdfd38e915d6e86bdbe9d7529b7587c6b Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Sat, 18 Jan 2025 10:25:53 -0500 Subject: [PATCH 4/5] hide unstable channel show in doctests --- base/channels.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/base/channels.jl b/base/channels.jl index 527c22b3d45fd..ef508bd40e3ed 100644 --- a/base/channels.jl +++ b/base/channels.jl @@ -130,8 +130,7 @@ julia> chnl = Channel{Char}(1, spawn=true) do ch for c in "hello world" put!(ch, c) end - end -Channel{Char}(1) (2 items available) + end; julia> String(collect(chnl)) "hello world" From bd51b42ae644fd4f97d698628e5a66de276c07a6 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Sat, 18 Jan 2025 10:31:55 -0500 Subject: [PATCH 5/5] expand threads_exec tests to cover interactive threads --- test/threads.jl | 8 +++++--- test/threads_exec.jl | 10 +++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/test/threads.jl b/test/threads.jl index 179279dbab4e6..29a18b6c49930 100644 --- a/test/threads.jl +++ b/test/threads.jl @@ -74,9 +74,11 @@ end let cmd = `$(Base.julia_cmd()) --depwarn=error --rr-detach --startup-file=no threads_exec.jl` for test_nthreads in (1, 2, 4, 4) # run once to try single-threaded mode, then try a couple times to trigger bad races - new_env = copy(ENV) - new_env["JULIA_NUM_THREADS"] = string(test_nthreads) - run(pipeline(setenv(cmd, new_env), stdout = stdout, stderr = stderr)) + for test_nthreadsi in (0, 1) + new_env = copy(ENV) + new_env["JULIA_NUM_THREADS"] = string(test_nthreads, ",", test_nthreadsi) + run(pipeline(setenv(cmd, new_env), stdout = stdout, stderr = stderr)) + end end end diff --git a/test/threads_exec.jl b/test/threads_exec.jl index 65e5ef57c911b..134fca3fa877f 100644 --- a/test/threads_exec.jl +++ b/test/threads_exec.jl @@ -737,7 +737,7 @@ end @test_throws CompositeException _atthreads_with_error(zeros(threadpoolsize()), true) let a = zeros(threadpoolsize()) _atthreads_with_error(a, false) - @test a == [1:threadpoolsize();] + @test a == [threadpoolsize(:interactive) .+ (1:threadpoolsize());] end # static schedule @@ -748,8 +748,8 @@ function _atthreads_static_schedule(n) end return ids end -@test _atthreads_static_schedule(threadpoolsize()) == 1:threadpoolsize() -@test _atthreads_static_schedule(1) == [1;] +@test _atthreads_static_schedule(threadpoolsize()) == threadpoolsize(:interactive) .+ (1:threadpoolsize()) +@test _atthreads_static_schedule(1) == [threadpoolsize(:interactive) + 1;] @test_throws( "`@threads :static` cannot be used concurrently or nested", @threads(for i = 1:1; _atthreads_static_schedule(threadpoolsize()); end), @@ -793,7 +793,7 @@ function _atthreads_static_dynamic_schedule() end return ids, inc[] end -@test _atthreads_static_dynamic_schedule() == (1:threadpoolsize(), threadpoolsize() * threadpoolsize()) +@test _atthreads_static_dynamic_schedule() == (threadpoolsize(:interactive) .+ (1:threadpoolsize()), threadpoolsize() * threadpoolsize()) # errors inside @threads :dynamic function _atthreads_dynamic_with_error(a) @@ -867,7 +867,7 @@ function _atthreads_static_greedy_schedule() end return ids, inc[] end -@test _atthreads_static_greedy_schedule() == (1:threadpoolsize(), threadpoolsize() * threadpoolsize()) +@test _atthreads_static_greedy_schedule() == (threadpoolsize(:interactive) .+ (1:threadpoolsize()), threadpoolsize() * threadpoolsize()) # errors inside @threads :greedy function _atthreads_greedy_with_error(a)