Skip to content

Commit fbe8656

Browse files
Default to 1 interactive thread (#57087)
1 parent d5523b6 commit fbe8656

File tree

12 files changed

+143
-95
lines changed

12 files changed

+143
-95
lines changed

NEWS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ New language features
2929
Language changes
3030
----------------
3131

32+
- Julia now defaults to 1 "interactive" thread, in addition to the 1 "default" worker thread. i.e. `-t1,1`
33+
This means in default configuration the main task and repl (when in interactive mode), which both run on
34+
thread 1, now run within the `interactive` threadpool. Also the libuv IO loop runs on thread 1,
35+
helping efficient utilization of the "default" worker threadpool, which is what `Threads.@threads` and a bare
36+
`Threads.@spawn` uses. Use `0` to disable the interactive thread i.e. `-t1,0` or `JULIA_NUM_THREADS=1,0`, or
37+
`-tauto,0` etc. The zero is explicitly required to disable it, `-t2` will set the equivalent of `-t2,1` ([#57087])
38+
3239
- When methods are replaced with exactly equivalent ones, the old method is no
3340
longer deleted implicitly simultaneously, although the new method does take
3441
priority and become more specific than the old method. Thus if the new

base/channels.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,7 @@ julia> chnl = Channel{Char}(1, spawn=true) do ch
130130
for c in "hello world"
131131
put!(ch, c)
132132
end
133-
end
134-
Channel{Char}(1) (2 items available)
133+
end;
135134
136135
julia> String(collect(chnl))
137136
"hello world"

doc/src/manual/faq.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -941,7 +941,7 @@ While the streaming I/O API is synchronous, the underlying implementation is ful
941941

942942
Consider the printed output from the following:
943943

944-
```jldoctest
944+
```
945945
julia> @sync for i in 1:3
946946
Threads.@spawn write(stdout, string(i), " Foo ", " Bar ")
947947
end
@@ -954,7 +954,7 @@ yields to other tasks while waiting for that part of the I/O to complete.
954954
`print` and `println` "lock" the stream during a call. Consequently changing `write` to `println`
955955
in the above example results in:
956956

957-
```jldoctest
957+
```
958958
julia> @sync for i in 1:3
959959
Threads.@spawn println(stdout, string(i), " Foo ", " Bar ")
960960
end
@@ -965,7 +965,7 @@ julia> @sync for i in 1:3
965965

966966
You can lock your writes with a `ReentrantLock` like this:
967967

968-
```jldoctest
968+
```
969969
julia> l = ReentrantLock();
970970
971971
julia> @sync for i in 1:3

doc/src/manual/methods.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ Start some other operations that use `f(x)`:
615615
julia> g(x) = f(x)
616616
g (generic function with 1 method)
617617
618-
julia> t = Threads.@spawn f(wait()); yield();
618+
julia> t = @async f(wait()); yield();
619619
```
620620

621621
Now we add some new methods to `f(x)`:
@@ -640,7 +640,7 @@ julia> g(1)
640640
julia> fetch(schedule(t, 1))
641641
"original definition"
642642
643-
julia> t = Threads.@spawn f(wait()); yield();
643+
julia> t = @async f(wait()); yield();
644644
645645
julia> fetch(schedule(t, 1))
646646
"definition for Int"

doc/src/manual/multi-threading.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ of Julia multi-threading features.
55

66
## Starting Julia with multiple threads
77

8-
By default, Julia starts up with a single thread of execution. This can be verified by using the
9-
command [`Threads.nthreads()`](@ref):
8+
By default, Julia starts up with 2 threads of execution; 1 worker thread and 1 interactive thread.
9+
This can be verified by using the command [`Threads.nthreads()`](@ref):
1010

1111
```jldoctest
12-
julia> Threads.nthreads()
12+
julia> Threads.nthreads(:default)
13+
1
14+
julia> Threads.nthreads(:interactive)
1315
1
1416
```
1517

@@ -22,13 +24,20 @@ The number of threads can either be specified as an integer (`--threads=4`) or a
2224
(`--threads=auto`), where `auto` tries to infer a useful default number of threads to use
2325
(see [Command-line Options](@ref command-line-interface) for more details).
2426

27+
See [threadpools](@ref man-threadpools) for how to control how many `:default` and `:interactive` threads are in
28+
each threadpool.
29+
2530
!!! compat "Julia 1.5"
2631
The `-t`/`--threads` command line argument requires at least Julia 1.5.
2732
In older versions you must use the environment variable instead.
2833

2934
!!! compat "Julia 1.7"
3035
Using `auto` as value of the environment variable [`JULIA_NUM_THREADS`](@ref JULIA_NUM_THREADS) requires at least Julia 1.7.
3136
In older versions, this value is ignored.
37+
38+
!!! compat "Julia 1.12"
39+
Starting by default with 1 interactive thread, as well as the 1 worker thread, was made as such in Julia 1.12
40+
3241
Lets start Julia with 4 threads:
3342

3443
```bash
@@ -96,10 +105,17 @@ using Base.Threads
96105
Interactive tasks should avoid performing high latency operations, and if they
97106
are long duration tasks, should yield frequently.
98107

99-
Julia may be started with one or more threads reserved to run interactive tasks:
108+
By default Julia starts with one interactive thread reserved to run interactive tasks, but that number can
109+
be controlled with:
100110

101111
```bash
102112
$ julia --threads 3,1
113+
julia> Threads.nthreads(:interactive)
114+
1
115+
116+
$ julia --threads 3,0
117+
julia> Threads.nthreads(:interactive)
118+
0
103119
```
104120

105121
The environment variable [`JULIA_NUM_THREADS`](@ref JULIA_NUM_THREADS) can also be used similarly:

src/jloptions.c

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -623,8 +623,13 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
623623
break;
624624
case 't': // threads
625625
errno = 0;
626-
jl_options.nthreadpools = 1;
627-
long nthreads = -1, nthreadsi = 0;
626+
jl_options.nthreadpools = 2;
627+
// By default:
628+
// default threads = -1 (== "auto")
629+
long nthreads = -1;
630+
// interactive threads = 1, or 0 if generating output
631+
long nthreadsi = jl_generating_output() ? 0 : 1;
632+
628633
if (!strncmp(optarg, "auto", 4)) {
629634
jl_options.nthreads = -1;
630635
if (optarg[4] == ',') {
@@ -633,10 +638,9 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
633638
else {
634639
errno = 0;
635640
nthreadsi = strtol(&optarg[5], &endptr, 10);
636-
if (errno != 0 || endptr == &optarg[5] || *endptr != 0 || nthreadsi < 1 || nthreadsi >= INT16_MAX)
637-
jl_errorf("julia: -t,--threads=auto,<m>; m must be an integer >= 1");
641+
if (errno != 0 || endptr == &optarg[5] || *endptr != 0 || nthreadsi < 0 || nthreadsi >= INT16_MAX)
642+
jl_errorf("julia: -t,--threads=auto,<m>; m must be an integer >= 0");
638643
}
639-
jl_options.nthreadpools++;
640644
}
641645
}
642646
else {
@@ -650,17 +654,18 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
650654
errno = 0;
651655
char *endptri;
652656
nthreadsi = strtol(&endptr[1], &endptri, 10);
653-
if (errno != 0 || endptri == &endptr[1] || *endptri != 0 || nthreadsi < 1 || nthreadsi >= INT16_MAX)
654-
jl_errorf("julia: -t,--threads=<n>,<m>; n and m must be integers >= 1");
657+
// Allow 0 for interactive
658+
if (errno != 0 || endptri == &endptr[1] || *endptri != 0 || nthreadsi < 0 || nthreadsi >= INT16_MAX)
659+
jl_errorf("julia: -t,--threads=<n>,<m>; m must be an integer ≥ 0");
660+
if (nthreadsi == 0)
661+
jl_options.nthreadpools = 1;
655662
}
656-
jl_options.nthreadpools++;
657663
}
658664
jl_options.nthreads = nthreads + nthreadsi;
659665
}
660666
int16_t *ntpp = (int16_t *)malloc_s(jl_options.nthreadpools * sizeof(int16_t));
661667
ntpp[0] = (int16_t)nthreads;
662-
if (jl_options.nthreadpools == 2)
663-
ntpp[1] = (int16_t)nthreadsi;
668+
ntpp[1] = (int16_t)nthreadsi;
664669
jl_options.nthreads_per_pool = ntpp;
665670
break;
666671
case 'p': // procs

src/threading.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -698,15 +698,15 @@ void jl_init_threading(void)
698698
// and `jl_n_threads_per_pool`.
699699
jl_n_threadpools = 2;
700700
int16_t nthreads = JULIA_NUM_THREADS;
701-
int16_t nthreadsi = 0;
701+
// if generating output default to 0 interactive threads, otherwise default to 1
702+
int16_t nthreadsi = jl_generating_output() ? 0 : 1;
702703
char *endptr, *endptri;
703704

704705
if (jl_options.nthreads != 0) { // --threads specified
705706
nthreads = jl_options.nthreads_per_pool[0];
706707
if (nthreads < 0)
707708
nthreads = jl_effective_threads();
708-
if (jl_options.nthreadpools == 2)
709-
nthreadsi = jl_options.nthreads_per_pool[1];
709+
nthreadsi = (jl_options.nthreadpools == 1) ? 0 : jl_options.nthreads_per_pool[1];
710710
}
711711
else if ((cp = getenv(NUM_THREADS_NAME))) { // ENV[NUM_THREADS_NAME] specified
712712
if (!strncmp(cp, "auto", 4)) {
@@ -722,13 +722,16 @@ void jl_init_threading(void)
722722
}
723723
if (*cp == ',') {
724724
cp++;
725-
if (!strncmp(cp, "auto", 4))
725+
if (!strncmp(cp, "auto", 4)) {
726726
nthreadsi = 1;
727+
cp += 4;
728+
}
727729
else {
728730
errno = 0;
729731
nthreadsi = strtol(cp, &endptri, 10);
730732
if (errno != 0 || endptri == cp || nthreadsi < 0)
731-
nthreadsi = 0;
733+
nthreadsi = 1;
734+
cp = endptri;
732735
}
733736
}
734737
}

stdlib/REPL/test/replcompletions.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,7 +1187,7 @@ let s, c, r
11871187
REPL.REPLCompletions.next_cache_update = 0
11881188
end
11891189
c,r = test_scomplete(s)
1190-
wait(REPL.REPLCompletions.PATH_cache_task::Task) # wait for caching to complete
1190+
timedwait(()->REPL.REPLCompletions.next_cache_update != 0, 5) # wait for caching to complete
11911191
c,r = test_scomplete(s)
11921192
@test "tmp-executable" in c
11931193
@test r == 1:9
@@ -1221,7 +1221,7 @@ let s, c, r
12211221
REPL.REPLCompletions.next_cache_update = 0
12221222
end
12231223
c,r = test_scomplete(s)
1224-
wait(REPL.REPLCompletions.PATH_cache_task::Task) # wait for caching to complete
1224+
timedwait(()->REPL.REPLCompletions.next_cache_update != 0, 5) # wait for caching to complete
12251225
c,r = test_scomplete(s)
12261226
@test ["repl-completion"] == c
12271227
@test s[r] == "repl-completio"

test/gcext/gcext-test.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ end
3131
# @test success(p)
3232
errlines = fetch(err_task)
3333
lines = fetch(out_task)
34-
@test length(errlines) == 0
34+
@test isempty(errlines)
3535
# @test length(lines) == 6
3636
@test length(lines) == 5
3737
@test checknum(lines[2], r"([0-9]+) full collections", n -> n >= 10)

test/gcext/gcext.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,13 @@ int main()
600600
jl_gc_set_cb_notify_external_alloc(alloc_bigval, 1);
601601
jl_gc_set_cb_notify_external_free(free_bigval, 1);
602602

603+
// single threaded mode
604+
// Note: with -t1,1 a signal 10 occurs in task_scanner
605+
jl_options.nthreadpools = 1;
606+
jl_options.nthreads = 1;
607+
int16_t ntpp[] = {jl_options.nthreads};
608+
jl_options.nthreads_per_pool = ntpp;
609+
603610
jl_init();
604611
if (jl_gc_enable_conservative_gc_support() < 0)
605612
abort();

test/threads.jl

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,24 @@ let e = Event(true), started1 = Event(true), started2 = Event(true), done = Even
7272
end
7373
end
7474

75-
let cmd = `$(Base.julia_cmd()) --depwarn=error --rr-detach --startup-file=no threads_exec.jl`
76-
for test_nthreads in (1, 2, 4, 4) # run once to try single-threaded mode, then try a couple times to trigger bad races
75+
76+
let cmd1 = `$(Base.julia_cmd()) --depwarn=error --rr-detach --startup-file=no threads_exec.jl`,
77+
cmd2 = `$(Base.julia_cmd()) --depwarn=error --rr-detach --startup-file=no -e 'print(Threads.threadpoolsize(:default), ",", Threads.threadpoolsize(:interactive))'`
78+
for (test_nthreads, test_nthreadsi) in (
79+
(1, 0),
80+
(1, 1),
81+
(2, 0),
82+
(2, 1),
83+
(4, 0),
84+
(4, 0)) # try a couple times to trigger bad races
7785
new_env = copy(ENV)
78-
new_env["JULIA_NUM_THREADS"] = string(test_nthreads)
79-
run(pipeline(setenv(cmd, new_env), stdout = stdout, stderr = stderr))
86+
new_env["JULIA_NUM_THREADS"] = string(test_nthreads, ",", test_nthreadsi)
87+
run(pipeline(setenv(cmd1, new_env), stdout = stdout, stderr = stderr))
88+
threads_config = "$test_nthreads,$test_nthreadsi"
89+
# threads set via env var
90+
@test chomp(read(setenv(cmd2, new_env), String)) == threads_config
91+
# threads set via -t
92+
@test chomp(read(`$cmd2 -t$test_nthreads,$test_nthreadsi`, String)) == threads_config
8093
end
8194
end
8295

0 commit comments

Comments
 (0)