Unexpected behavior when using Threads.@spawn

Hi all,

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?

Your code outright crashes Julia on MacOS 13.5.1 :confused:

[13745] signal (11.2): Segmentation fault: 11
in expression starting at REPL[10]:1
glBindBuffer at /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib (unknown line)
glBindBuffer at /Users/skleinbo/.julia/packages/ModernGL/yNrOu/src/functionloading.jl:64 [inlined]
bind at /Users/skleinbo/.julia/packages/GLMakie/toRaR/src/GLAbstraction/GLBuffer.jl:32
Allocations: 15546846 (Pool: 15543498; Big: 3348); GC: 22
[1]    13744 segmentation fault  julia --threads=4

However, moving display(f) inside the first task, makes it work nicely!

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.

Hmm, like I said, running display in the same task as the updating loop, i.e.

Threads.@spawn begin
   display(f)
    while !exit[]
		p[] = [Point2f(randn(2)) for _ in 1:1000]
		sleep(0.1)
	end
end

works fine for me. A plot window opens and is updated for three seconds.

Maybe @sdanisch can help.

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.

1 Like

Thank you, I think that’s the most likely explanation.

Guess I’ll have to use @async with any plotting related code for now just to be safe and make my own code to be thread safe for using Threads.@spawn

Maybe also fill an issue with GLMakie?