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.)