Since Threads.@spawn is encouraged over @async according to doc, so I decided to convert all @async to Threads.@spawn when reusing some of my old code.
However, I’m getting some unexpected behaviors, for example when updating GLMakie figure.
Here is a MWE where a figure is being updated for 3 seconds and then stop: (julia: 1.9.3, GLMakie: v0.8.8)
using GLMakie
function main()
p = Observable(Point2f[(NaN, NaN)])
f, _ = scatter(p)
xlims!(-10, 10)
ylims!(-10, 10)
display(f)
exit = Threads.Atomic{Bool}(false)
@sync begin
# @async always works here
Threads.@spawn begin
while !exit[]
p[] = [Point2f(randn(2)) for _ in 1:1000]
sleep(0.1)
end
end
Threads.@spawn begin
sleep(3)
exit[] = true
end
end
end
If julia start with only 1 thread, then everything works; figure updates normally
If julia start with more than 1 threads, then the figure will only being updated for a short duration (less than 3 second). The more thread julia has, the quicker the figure stop updating.
If @async is used to replace the first Threads.@spawn, then it always works regardless how many threads julia has
Am I using Threads.@spawn correctly? Or is it something else?
Interesting, I was also getting segfault on my desktop before. But after some updates (graphic driver, julia 1.9.2→1.9.3, …) it stops crashing. However, the problem of figure noting getting updated still remains.
Correction: I checked again, the MWE still segfault with the same error on my PC (linux 6.1.50-1-lts) but @async version works fine. Moving display(f) inside the first task did not work for me.
On my laptop the Threads.@async did not segfault, but the figure is still not updating properly. Really strange.
For me, I have to start with julia -t 1 for this to work.
If julia start with more than 1 thread, the figure will either not being updated or just segfault with the same error in your previous post.
But if @async is used, then everything is complete fine, regardless where you put the display.
So I guess the question is why Threads.@spawn and @async behave so differently in this case. I would expect then to be the same (in this example at least).
Yes, I understand. As I said, it works for me with -t 4 or such.
Check on which threads the respective tasks are running with Threads.threadid() during the loop. Maybe they migrate or something.
@async schedules a task on the calling thread, i.e. in this case always on thread 1.
function main()
p = Observable(Point2f[(NaN, NaN)])
f, _ = scatter(p)
xlims!(-10, 10)
ylims!(-10, 10)
#display(f)
exit = Threads.Atomic{Bool}(false)
@sync begin
# @async always works here
Threads.@spawn begin
display(f)
while !exit[]
@info "Rand task on thread $(Threads.threadid())"
p[] = [Point2f(randn(2)) for _ in 1:1000]
sleep(0.2)
end
end
Threads.@spawn begin
@info "Exit task on thread $(Threads.threadid())"
sleep(2)
exit[] = true
end
end
end
main (generic function with 1 method)
julia> main()
[ Info: Exit task on thread 2
[ Info: Rand task on thread 1
[ Info: Rand task on thread 1
[ Info: Rand task on thread 1
[ Info: Rand task on thread 1
[ Info: Rand task on thread 1
[ Info: Rand task on thread 1
[ Info: Rand task on thread 1
[ Info: Rand task on thread 1
[ Info: Rand task on thread 1
[ Info: Rand task on thread 1
Task (done) @0x0000000147fe1c30
I suspect something in the pipeline from Observables to OpenGL isn’t thread safe, but I couldn’t tell what.