Let’s not confuse threads with tasks here. Primarily, ThreadPinning.jl is for pinning Julia threads to specific cores. That’s it. Whether tasks are started on and/or stay on a certain Julia thread is an entirely different thing.
First things first: Julia implements task-based multithreading (somewhat similar to Go), so the general idea is that users care about tasks and not so much about threads. You just @spawn
whatever you want and let the scheduler put this on a certain thread or, if necessary, let the task migrate to another thread if necessary. In some sense, this is great since it abstracts away some of the more low-level scheduling aspects and also facilitates composable / nested multithreading.
However, sometimes you want / need to control on which Julia thread (and, if pinned to a core in some way, which core) a task runs on. The good news is, this is still possible (see below). However, I must say that my feeling is that it is not really supported since it might undermine the idea of composable task-based multithreading (personally, I think we should support both!).
So, how can we “pin” a task to a certain Julia thread? The keyword here is sticky
. If you look at the source code of @threads
(in particular this function), you see that the task(s) corresponding to the body of the multithreaded loop will get put on specific threads and, importantly, get the property sticky = true
. This means, that the task will stay on this Julia thread and won’t migrate away. So, if you write a loop like
@threads for i in 1:nthreads()
f(i)
end
you can be sure that f(i)
will run on the Julia thread i
and will stay there. Contrast this to @spawn
which has sticky = false
. Hence, tasks spawned in that way can, in principle, migrate between threads.
Now you might say, well that’s great but I sometimes want to “spawn” a single task on a specific thread… While you could use @threads
for this,
@threads for i in 1:nthreads()
if i == thread_that_should_run_the_computation
f()
end
end
this is arguably overly complex and also somewhat of a misuse of @threads
. Fortunately, we can essentially just define our own hybrid of @threads
and @spawn
with sticky = true
. That’s what @tspawnat
is which I’ve added to ThreadPinning.jl yesterday (note that ThreadPools.jl also has a @tspawnat
but, counterintuitively, it has sticky = false
, see this issue). So, you can now just do
using ThreadPinning
t = @tspawnat 3 f()
[...]
fetch(t)
to run f()
on the third Julia thread without any task migration.
(Note that while the thread pinning part of ThreadPinning.jl only supports Linux, @tspawnat
can, of course, be used on any system since it is only about making Julia tasks sticky.)