@take macro?

I’m using a Channel to keep a few instances of a struct that I need to use inside a thread (following the instructions in Thread-Safe Storage · OhMyThreads.jl):

 using OhMyThreads
 
 nthings = 4
 const things = Channel{MyThing}(nthings)
 foreach(1:nthings) do _
     put!(things, MyThing()) 
 end
 
 tforeach(some_iter) do _
     thing = take!(things)
     results = thing(some_data)
     put!(things, thing)
 end

It strikes me that the pattern of take!ing something, using it, and then put!ing it back is very similar to the @lock, the do block in open, etc, where I don’t need to explicitly state that I’m unlocking, closing, or putting things back.

Do other people find this pattern common, and would people find it useful if we indeed had a macro or do-block for this specific use-case?

1 Like

This doesn’t look like a job for a macro. A function should be plenty sufficient.

Whether or not something exists, it’s very easy to write this functionality yourself. (Or so I think – I never really work with Channel so don’t know whether I’m neglecting something important.)

function takeput!(fun, channel)
	x = take!(channel)
	y = try
		fun(x)
	finally
		put!(channel, x) # always return the value to the channel, even with an error
	end
	return y
end

The try-finally part could be replaced with straight code if you don’t need to ensure the value goes back on the channel after an exception.

You would write your tforeach as

tforeach(some_iter) do _
    results = takeput!(thing, things)
end

EDIT: Oops, the code I wrote here swaps the role of the function and argument relative to the original prompt. The poster below fixes my mistake.

Whether or not something exists, it’s very easy to write this functionality yourself.

I agree. I should have put it differently:

Do other people find this pattern common, and would people find it useful if we indeed had a macro or do-block for this specific use-case (I will edit the original post)?

Drawing inspiration from your example, and looking in Base at how the open do-block looks like, resulted in this:

function borrow(f::Function, c::Channel)
    v = take!(c)
    try
        return f(v)
    finally
        put!(c, v)
    end
end

Which allows us to do things like:

results = borrow(channel) do thing
    thing(some_data)
end