Hello world, and Julia behaves strangely

Hi,

I’m new to this community, so hello everyone!

I’m learning Julia and I get some strange output on a very simple program. I don’t understand what I’m doing wrong. Could someone help me please?

This is an experiment with a task and a channel., here is the code.

chan = Channel(1)

waiter = @task begin

    while true

        wait(chan)
        msg = take!(chan)
        println("got ", msg)
        if msg == "stop" break end
    end
end

sleep(2)
schedule(waiter)
println("scheduled")

sleep(1)
println("sending string msg")
put!(chan, "message")

# sleep(1)
# println("sending number msg")
# put!(chan, 123)

I get the following result below. Notice the extra double-quote that shouldn’t be there after the word message:

julia> include("test.jl")
scheduled
sending string msg
got message"
message"

And if I uncomment the last 3 lines to add a second message, it gets even weirder, the double quote disappears and the number 123 is repeated twice:

julia> include("test.jl")
scheduled
sending string msg
got message
sending number msg
got 123123

So I guess there must be something I’m doing wrong, I keep reading the docs, but I don’t understand why it behaves like this.
Julia Version 1.7.2 (2022-02-06) in a terminal of VSCode on Windows.

Since you run it in the repl it will always print the value of the evaluated expression, here it seems like put! might also return the value it inserts and that is maybe what is added to the end.
The formatting of the last lines seems a bit strange, but on the phone so cant test it.

1 Like

Wait I got it. There’s a newline at the beginning of the string which is the result of the call. Same for 123, I get 123 from the waiter, and then 123 as a return value of the call.

Thanks a lot!

Sorry, I don’t follow: where did the nl come from?

If you have a script and want to suppress the output of the value of the last statement, just add

nothing

as last line.

Same holds for functions, if they shall not return anything use nothing as last statement.

Thanks for the tip.

I don’t know where the newline comes from. To me, it shouldn’t be there. The newline explains the output, but the newline itself is not explained.

Or

return nothing 

Some think that this is better style.

Understood, thank you!

You have a race condition in your output to stdout.

The newline, you don’t expect is from your waiter tasks println command.

The race condition, which produces strange behavior, is that you use stdout io stream in both threads (main+waiter). Additional to your println commands, show is called for the return value of put!.
The channels put! and take! are thread-safe as they call lock/unlock on the channel, but println is not, because the io stream STDOUT, which is typically buffered too, is not synchonized via lock/unlock.

You can have it even more weird, for example, if you supress the show output, by putting a ; at the end of the put! command:

julia> chan = Channel(1)
Channel{Any}(1) (empty)

julia> waiter = @task begin
           while true
               wait(chan)
               msg = take!(chan)
               println("got ",msg)
               if msg == "stop" break end
           end
       end
Task (runnable) @0x000000000b7d2e90

julia> schedule(waiter)
Task (runnable) @0x000000000b7d2e90

julia> put!(chan, "xxxxx");
got
julia>

The output of println("got ",msg) is at some time point disrupted by clearing STDOUT, so only "got " made it through. But if you change
println("got ",msg)
to
println("got "*msg)
the output gets through:

julia> chan = Channel(1)
Channel{Any}(1) (empty)

julia> waiter = @task begin
           while true
               wait(chan)
               msg = take!(chan)
               println("got "*msg)
               if msg == "stop" break end
           end
       end
Task (runnable) @0x000000000b202e90

julia> schedule(waiter)
Task (runnable) @0x000000000b202e90

julia> put!(chan, "xxxxx");
got xxxxx

julia> 

because the complete output string is composed before writing to STDOUT and writing to STDOUT is done once by println. But beware, using string concatenation before calling println is NOT the general solution here, because it depends on the buffer size of STDOUT. If several threads needs to write to STDOUT you have to synchronize it on your own.

If you change your original code from
println("got ", msg)
to
print("got "*msg*"\n")
you probably wouldn’t have started this thread :slight_smile:

julia> chan = Channel(1)
Channel{Any}(1) (empty)

julia> waiter = @task begin
           while true
               wait(chan)
               msg = take!(chan)
               print("got "*msg*"\n")
               if msg == "stop" break end
           end
       end
Task (runnable) @0x000000000b242e90

julia> schedule(waiter)
Task (runnable) @0x000000000b242e90

julia> put!(chan, "message")
got message
"message"

All looks good, but it is only because buffer size is large enough for these short examples.

So, you are messing with stdout in a non thread-safe way and that produces weird outputs.

1 Like

Right, with additional “sleep” it works:

chan = Channel(1)
waiter = @task begin
    while true
        wait(chan)
        msg = take!(chan)
        println("got ", msg)
        if msg == "stop" break end
    end
end

sleep(2)
schedule(waiter)
println("scheduled")

sleep(1)
println("sending string msg")
put!(chan, "message")

sleep(4)
println("oo")

nothing

gives

julia> include("junk.jl")                                          
scheduled                                                          
sending string msg                                                 
got message                                                        
oo                                                                 
                                                                   
julia> 

As @oheil explained, the printing needs to be separated from the asynchronous execution.

I’m surprised, I didn’t expect that. I guess my past with Javascript explains my mistake. In Julia, parallel is… really parallel.

So the general solution is to take care of it, properly handling concurrent access to STDOUT, by setting up a synchronizing mechanism. For example, a channel with a consumer that writes messages to STDOUT, if I understand correctly.

Thanks a lot for the time you took to explain it. You help me think in Julia.
:slight_smile:

2 Likes