Hi @dfdx, and many thanks for your time on this.
I want to move on with the Julia manual so I did try a few things w.r.t tasks in order to be confident that I more or less understand coroutines in Julia.
In your code everything runs in the same task and thus is serial. No consumer will be able to read from the channel before producer finishes it’s job. If channel’s buffer isn’t big enough to keep all that producer creates, execution will just hang on.
I don’t think this is right? My example was basically a modification of the code in https://docs.julialang.org/en/latest/manual/control-flow/#man-tasks-1, for producer/consumer stuff, and the consumer reads fine from the unbuffered channel.
(note, though, that all tasks will still run on the same CPU; if you really want to distribute work, consider multiple processes and RemoteChannel).
yes, I understand this. Next step will be RemoteChannel, but I wanted to grasp this before moving on.
What comes below is perhaps going to be a bit too verbose and too basic if familiar with tasks, but since I was asking for more examples, perhaps this is useful to others (myself in the future included). I’m sure things can be written better/more elegantly, so any suggestions/comments are welcome.
- First example, creating tasks “manually”. Two functions are defined as tasks e1t1 and e2t2 so that when they reach the “sleep” call they will yield to the scheduler and thus we can do progress in both at the same time. And we can synchronize with istaskdone. Execute by calling e1_t()
function e1_t()
e1t1 = Task(e1_t1)
e1t2 = Task(e1_t2)
schedule(e1t1)
schedule(e1t2)
while istaskdone(e1t1) == false || istaskdone(e1t2) == false
sleep(1)
end
println("All finished")
end
function e1_t1()
for i=1:5
println("Task 1: $i")
sleep(2)
end
end
function e1_t2()
for i=1:5
println("Task 2: $i")
sleep(5)
end
end
- Second example, the example given by @dfdx to grab URLs asynchronously. I want to understand what @sync and @async do, so I give here the example with those macros, and then my version creating the tasks manually.
# With macros
function g_macros()
urls = ["https://discourse.julialang.org/" for i=1:10]
results = Vector(10)
@sync for (i, url) in enumerate(urls)
@async results[i] = Requests.get(url)
end
results
end
# The same idea, but manually creating the tasks. Execute by calling g()
function get(url,i,results)
results[i] = Requests.get(url)
end
function g()
urls = ["https://discourse.julialang.org/" for i=1:10]
tasks = Vector(10)
results = Vector(10)
for (i,url) in enumerate(urls)
tasks[i] = schedule(Task(()->get(url,i,results)))
end
while any(map(!istaskdone,tasks))
sleep(0.01)
end
results
end
- Third example. Consumer/producer stuff.
# Execute by calling myconsumer(), who gets items produced by myproducer as soon as they are ready
function myproducer(c::Channel)
for n=1:10
println("Generating a new item")
put!(c, n)
end
end
function myconsumer()
cha = Channel(myproducer)
results = Vector(10)
ind = 1
for i in cha
results[ind]=i
ind += 1
end
results
end
# But just in case, the task doesn't have to be the producer, it could be the consumer as well.
# Execute by calling myproducer2(), which will put items in the channel, consumed as available
# by the task myconsumer2
function myproducer2()
cha = Channel(myconsumer2)
for n=1:10
put!(cha,n)
end
end
function myconsumer2(c::Channel)
for i in c
println("Got $i")
end
end
- Fourth example. Bind multiple tasks to a channel. Not sure if this is what is meant in the manual, but having several tasks listening on a channel we can write master-worker style programs, where the master gives several tasks and the several workers process them as soon as they are idle. In this case I have only two workers (tasks w1 and w2), and we would execute this by calling master()
# For master-workers type problems, where I want several workers to work on the tasks given by the master
function master()
cha = Channel(0)
w1 = Task(()->worker(1,cha)) ; w2 = Task(()->worker(10,cha))
schedule(w1) ; schedule(w2)
for n=1:20
put!(cha,n)
end
close(cha)
while istaskdone(w1) == false || istaskdone(w2) == false
sleep(0.01)
end
println("All finished")
end
function worker(sleep_s,c::Channel)
for i in c
sleep(sleep_s)
println("Got $i and slept $sleep_s")
end
end
- Fifth example. Bind multiple channels to a task. In this example a producer (could be many producers) generates values and are put at random in different channels (either cha1 or cha2). The consumer waits for a value in either channel and processes it.
# Output to different channels at random, which are consumed by the consum as needed.
# Execute by calling consum()
function produc(cha1::Channel,cha2::Channel)
for i=1:20
if bitrand()[1] == true
put!(cha1,rand()*3)
else
put!(cha2,rand()*3)
end
end
close(cha1)
close(cha2)
end
function consum()
cha1 = Channel(0)
cha2 = Channel(0)
pr = Task(()->produc(cha1,cha2))
schedule(pr)
while isopen(cha1) || isopen(cha2)
sleep(0.01)
if isready(cha1)
v = take!(cha1)
println("Got $v from cha1" ) ; sleep(v)
end
if isready(cha2)
v = take!(cha2)
println("Got $v from cha2") ; sleep(v)
end
end
end