Work on main thread delays spawned task

Consider the following example:

function ex1()
    task = @Threads.spawn begin
        st = time()
        sleep(1.0)
        return time() - st
    end

    st2 = time()
    while time() - st2 < 5 end

    println("task slept for $(fetch(task)) seconds")
end

When I start Julia 1.7.1 with julia -t2 and run ex1(), the result is “task slept for 5.0 seconds”.

With the following code, the result is “task slept for 1 seconds”:

function ex2()
    task = @Threads.spawn begin
        st = time()
        sleep(1.0)
        return time() - st
    end

    @Threads.spawn begin
        st2 = time()
        while time() - st2 < 5 end
    end

    println("task slept for $(fetch(task)) seconds")
end

What is happening here? Why does the task sleep for 5 seconds in the first case?

I understand that @Threads.spawn can spawn the task onto thread 1 (discussed here: https://github.com/JuliaLang/julia/issues/34267). But the delay occurs even if the task is spawned to thread 2. Also, this wouldn’t explain why ex2() never seems to have a delay.

1 Like

Not quite the same for repeated runs

$ julia -t 2

julia> includet("tasks.jl")

julia> ex1()
task slept for 4.9923179149627686 seconds
julia> ex1()
task slept for 5.000078916549683 seconds

julia> ex2()
task slept for 1.0020849704742432 seconds
julia> ex2()
task slept for 5.0001060962677 seconds

julia> ex1()
task slept for 5.000100135803223 seconds
julia> ex2()
task slept for 5.000117063522339 seconds

and the ex2 behaviour is consistent

$ julia -t 2
julia> includet("tasks.jl")
julia> ex2()
task slept for 1.00211501121521 seconds
julia> ex2()
task slept for 5.000128984451294 seconds

which I have no concrete explanation for, except perhaps delays due to compilation in the first invocation

You are right, after restarting Julia the timings are not consistent for me either.
However, why does the 5 second sleep happen at all?
Can I somehow coerce the task scheduling to consistently give me the expected result (the 1 second sleep)?

I don’t really know a lot of under-the-hood details of julia multithreading but my first guess was that it could be the case that your MulitThr. task gets scheduled on the main julia thread (the thread #1), since julia doesn’t treat the main thread in any special way. Hence, I would recommend using ThreadPools.spawnbg just to make sure that the background thread is indeed used. However, after the quick check, it was clear that it wasn’t it.
I bet that the problem is with the sleep function. If you do something like this instead of your original code it works as expected:

function ex1()
    task = Threads.@spawn begin
        st = time()
        while time() - st < 1
        end
        return time() - st
    end

    st2 = time()
    while time() - st2 < 5
    end

    return println("task slept for $(fetch(task)) seconds")
end
task slept for 1.0 seconds
task slept for 1.0 seconds
task slept for 1.0 seconds

I think there is some weird interaction with thread #1 (the main julia thread) inside the sleep function, since it was designed for the async tasks (green threads); hence it “blocks”(yields back to the event loop, or whatever it is called in julia) your spawned task and doesn’t continue working until the main thread yields back, which doesn’t happen until it calls fetch (exactly after 5 seconds).
I filed an issue regarding this, so hopefully it is going to be fixed.