I’ve been experimenting with something along the lines of Rust’s MPSC (multi-producer/single-consumer) channels. The result is here. With this shard you can still call send and receive just like with plain Channels, and if you want to do a non-blocking receive you call receive?.
Part of this experiment was based on the frustration some of us felt when things like Channel#empty? were removed from the API and I finally decided to create this shard instead of using code like channel.@queue.empty? any time I didn’t want to block when there were no items in the channel.
To be clear, I think removing methods like Channel#empty? was a good decision because they gave a false sense of security when you were consuming the same channel on multiple fibers. Most of my own use cases for channels in Crystal are consuming from a single fiber, though, and it seems a few other members of the Crystal community do the same. And one of the nice things about consuming from a single fiber is that you don’t have to worry about race conditions that the stdlib Channel insulates us from.
require "benchmark"
require "mpsc"
empty_channel = Channel(String).new(10)
empty_mpsc = MPSC::Channel(String).new
value = nil # Assigning to this variable on each iteration so LLVM doesn't optimize out the benchmark blocks
iterations = ENV.fetch("ITERATIONS", "1000").to_i
Benchmark.ips do |x|
x.report "MPSC::Channel" { iterations.times { value = empty_mpsc.receive? } }
x.report "Channel" do
iterations.times do
select
when value = empty_channel.receive?
else
value = nil
end
end
end
end
pp value # Use the assigned value so LLVM doesn't optimize the benchmark blocks out
# MPSC::Channel 2.09M (478.88ns) (± 0.75%) 0.0B/op fastest
# Channel 6.94k (144.20µs) (± 0.74%) 281kB/op 301.11× slower