My understanding is that without interpolation, there is a race condition whereby the object that x refers to might or might not change before the function runs. The interpolation “captures” the binding, guaranteeing that x refers to the object it was bound to at the time of the @spawn macro.
The code below (swapping out func(x) / func($x) demonstrates the difference.
import Base.Threads.@spawn
function func(A)
A[1] = 90
return A
end
const N = 100
is_same_object = Vector{Bool}(undef, N);
for i = 1:N
x = [1,2,3]
fx = @spawn func(x)
#fx = @spawn func($x)
x = [4,5,6]
x2 = fetch(fx)
is_same_object[i] = x2 === x
end
count(is_same_object)
If this interpretation is correct, then I think the documentation is misleading:
Values can be interpolated into @spawn via $, which copies the value directly into the constructed underlying closure. This allows you to insert the value of a variable, isolating the aysnchronous code from changes to the variable’s value in the current task.