Thank you for your tutorial, it is really great.
But, it is somewhat difficult to read, I’ll try to describe my issues, maybe it will help to improve it.
- in the first example, an argument of the
allocation
function is body
. This is very confusing, because it is a function, which is usually denoted with verb. Maybe it’s better to use word like process
or something similar?
function allocate(process)
open("/dev/urandom") do file
buffer = Vector{UInt8}(undef, buffer_length)
process((file, buffer))
end
end
- To be honest, worker pool construction which consists of three interwined closures is very hard to understand. It took me some time to understand, that first example can be rewritten as (I hope I am not mistaken and this is correct representation of the original idea)
let buffer_length = 2^10
ntasks = Threads.nthreads()
@sync for _ in 1:ntasks
@spawn begin
open("/dev/urandom") do file
buffer = Vector{UInt8}(undef, buffer_length)
for input in works
read!(file, buffer)
input[] = sum(buffer; init = 0.0)
end
end
end
end
sum(results) / (length(results) * buffer_length)
end
Maybe it makes sense to add something like Explanation
subsection, where worker pool pattern can be shown as in the snippet above and than step by step it can be transformed to the three functions version.
- Am I correct, that if you do not need to allocate any resources, you can just use
identity
instead of allocate
, i.e.
workerpool(identity, works) do ref
ref[] = sum(rand(100))
end
(as I have said, it’s not easy to correctly trace all variables and functions).
- I’ve tried to trace threads usage with the following modification
function workerpool(work!, allocate, request; ntasks = Threads.nthreads())
@sync for _ in 1:ntasks
@spawn allocate() do resource
cnt = 0
for input in request
cnt += 1
work!(input, resource)
end
@info Threads.threadid() cnt
end
end
end
and result was
┌ Info: 4
â”” cnt = 1
┌ Info: 3
â”” cnt = 1
┌ Info: 2
â”” cnt = 1
┌ Info: 1
â”” cnt = 29
so it looks like all work was done mostly by one thread. Is it a problem of a scheduler or in this particular example execution time was so small, that Julia just didn’t have time to parallelize task? Or it is the problem discussed in the next section “Re-distribution hacks”?
- In Re-distribution hacks it is written “use of
@async
impedes migration of the tasks across OS threads (which is not implemented as of Julia 1.6 but is likely to be implemented in the future Julia versions)”. What does that mean? That using @async
is not recommended because it schedule everything on one thread? But this behaviour may change in the future and this is what is said in brackets?