The following code is unsafe (or the result is undefined) in general because the loop variable i
may be changed when a new task is spawned and run:
using .Threads: @spawn
function loop(n)
tasks = Task[]
@sync for i in 1:n
push!(tasks, @spawn i)
end
return sum(fetch, tasks)
end
Instead, we should interpolate the variable with $
to copy the value, right?
You don't need interpolation in this case because the scope of i
is inside the loop and i
is never re-assigned. You'd have the problem if you are capturing variables that are re-assigned. For example:
julia> let tasks = []
a = 0
@sync for i in 1:3
a += i
push!(tasks, Threads.@spawn (i, a))
end
fetch.(tasks)
end
3-element Vector{Tuple{Int64, Int64}}:
(1, 6)
(2, 6)
(3, 6)
Using $a
(or let a = a ... end
) is a good solution here.
Thank you! So, we can think i
as a variable that is newly generated for each iteration and therefore iterations have its own isolated variable. I need to update my mental model.
Exactly! This is actually how closures work in Julia. Task-creating macros like @spawn
are just a thin syntax sugar on top of closures.
I think it's easier to play with closures and figure out how it interacts with the scoping rule:
julia> fs = []
for i in 1:3
push!(fs, () -> i)
end
[f() for f in fs]
3-element Vector{Int64}:
1
2
3
Last updated: Dec 28 2024 at 04:38 UTC