Printing in a background process

Hi everyone,

I’m trying to do something that feels like should be easy but I can’t figure it out.

Basically, let’s say I have a function (doer) which takes a while to run and I want to have another function printer printing stuff to stdout while doer is running - without messing with doer itself.

The motivation for this is that in Term.jl I need to have a function rendering progress bars information while user’s code runs. I can’t edit the user’s code and I want the progress bar rendering to happen continuously in the background.

This is a MWE of what I have so far:



mutable struct Status
    on::Bool
end


function printer(s::Status)
    println("Printer turning on")
    while s.on
        println("PRINTING")
        sleep(0.01)
    end
    println("Printer turning off")
end


function doer()
    # println("Doer getting started")
    s = Status(true)

    # @async printer(s)
    Base.Threads.@spawn printer(s)
    
    for k in 1:50000
        x = rand(100, 100)
        y = x.^2 .- rand(100, 100)
    end
    s.on = false
    
    nothing
end


print("\n\n")
doer()

My expectation is that printer should be running in the background while doer finishes its stuff, but this is what the output lokos like:

Printer turning on
Printer turning off

I’ve tried using @async and Task and @spawn… same result. The only thing that makes it work is adding a sleep call in the loop in doer.

Btw: Threads.nthreads() # 8.

Any idea on what I’m doing wrong?

Try

    for k in 1:50000
        x = rand(100, 100)
        y = x.^2 .- rand(100, 100)
        yield() # <-- Allows switching over to other tasks.
    end

Or better yet, thread the loop as well

Threads.@spawn begin 
    for k in 1:50000
        x = rand(100, 100)
        y = x.^2 .- rand(100, 100)
    end
    s.on = false
end
1 Like

I first tried accumulating from the loop so it couldn’t be removed by dead code elimination. That didn’t seem to make a difference. Then I remembered that GC pauses all threads, and I noticed the code was very allocating; I made the operations in the loop non-allocating, and suddenly got tons of printing.

using Random
function doer()
    # println("Doer getting started")
    s = Status(true)

    # @async printer(s)
    Base.Threads.@spawn printer(s)
    accum = 0.0
    x = zeros(100, 100)
    y = similar(x)
    for k in 1:50000
        rand!(x)
        @. y = x^2 - rand()
        accum += sum(y)
    end
    s.on = false

    return accum
end

edit: oops! This was just because I forgot to import Random; I lost the error in the sea of printing which never gets turned off… when I fix it, I don’t see the printing.

This might need https://github.com/JuliaLang/julia/pull/42302#issuecomment-1072785204 which is in Julia 1.9.

@spawn doer() also seems to work, I guess thread 1 have higher priority?

Which Julia version are you on? Your GC-free loop is just as blocking for me on Julia 1.7.2 (Windows).

I just realized I had a simple mistake; I edited the post. It is not working for me either now, 1.7.2 MacOS.

1 Like

Thanks everyon for looking into this, I really appreciate the help.

@skleinbo 'and @chengchingwen had it right: wrapping doer or its content in another @spaw makes it work like a charm, I’ve also added a wait on the task generated by @spawn doer() and it does exactly what I need. Progress bar in Term.jl won’t be hanging any more!

@ericphanson thanks for the help, unfortunately I can’t ensure that users’ code (which would be in doer) is not allocating!

Maybe it’s worth reading how ProgressMeter do or even wrap that? It also support multi-threading and is thread-safe.

1 Like

A similar topic was already discussed here:

Thanks, I looked for related topics but had missed this one.