Actually, i think my answer was incorrect; some slight variations recover the same underlying problem, even with a separate lock:
function showme(n, m)
c = Channel(m)
l = ReentrantLock()
t1 = @async begin
for x in 1:n
put!(c, 0)
sleep(0.1)
end
end
t2 = @async begin
lock(l)
for x in 1:n
put!(c,1)
sleep(0.001)
end
unlock(l)
end
d= []
sleep(1)
for _ in 1:2n
push!(d, take!(c))
end
show(d)
end
julia> showme(5,5)
Any[0, 1, 1, 1, 1, 1, 0, 0, 0, 0]
julia> showme(5,4)
Any[0, 1, 1, 1, 1, 0, 1, 0, 0, 0]
After digging in, it appears that lock(c::Channel)
calls lock(c.cond_take.lock)
, the underlying ReentrantLock
for the channel. And, your put!
call ends up nesting lock
calls (see https://github.com/JuliaLang/julia/blob/master/base/channels.jl, around line 313), and observe also:
julia> c = Channel(4)
Channel{Any}(4) (empty)
julia> islocked(c.cond_put.lock)
false
julia> lock(c)
julia> lock(c)
julia> islocked(c.cond_put.lock)
true
julia> unlock(c)
julia> islocked(c.cond_put.lock)
true
julia> unlock(c)
julia> islocked(c.cond_put.lock)
false
My intuition is that these nested locks (which I don’t quite understand) are interacting with you asynchronous code in unanticipated ways. Perhaps your lock
call in t2
is nesting within a lock that is blocking triggered by a put!
in t1
?
It looks you can lock the calls within t1
to get the behaviour you want:
function showme(n, m)
c = Channel(m)
t1 = @async begin
for _ in 1:n
lock(c)
put!(c, 0)
unlock(c)
sleep(0.1)
end
end
t2 = @async begin
lock(c)
for _ in 1:n
put!(c,1)
sleep(0.001)
end
unlock(c)
end
d= []
sleep(1)
for _ in 1:2n
push!(d, take!(c))
end
show(d)
end
julia> showme(5,5)
Any[0, 1, 1, 1, 1, 1, 0, 0, 0, 0]
julia> showme(5,4)
Any[0, 1, 1, 1, 1, 1, 0, 0, 0, 0]