From: "byroot (Jean Boussier)" Date: 2022-08-19T15:43:02+00:00 Subject: [ruby-core:109576] [Ruby master Feature#18965] Further Thread::Queue improvements Issue #18965 has been updated by byroot (Jean Boussier). So I implemented `pop(:count)` https://github.com/ruby/ruby/pull/6257 And benchmarked it against my somewhat realistic benchmark: https://github.com/Shopify/statsd-instrument/pull/315 The TL;DR; is that this benchmark is mostly bound on `UDPSocket#send` and some string concatenation, `Queue#pop(true)` only account for `0.3%` of runtime, so `pop(count: )` bringing that share down to `0.08%` doesn't make any visible difference on the overall benchmark. Whether or not that improvement would show on different (non-micro) benchmark is unclear. ---------------------------------------- Feature #18965: Further Thread::Queue improvements https://bugs.ruby-lang.org/issues/18965#change-98747 * Author: byroot (Jean Boussier) * Status: Open * Priority: Normal ---------------------------------------- Following the recent addition of a `timeout` parameter to `Queue#pop`, there are a handful of other improvements I'd like to make. ### Batch insert When using the queue for batch processing, it would be good to be able to push multiple elements at once: Currently you have to call `push` repeatedly ```ruby items.each do |item| queue.push(item) end ``` That's wasteful because on each call we check wether the queue is closed, try to wakeup blocked threads, etc. It would be much better if you could do: ```ruby queue.concat(items) ``` With of course both `nonblock` and `timeout` support. Then there's the question of how `SizedQueue` would behave if it's not full, but still doesn't have space for all the elements. e.g. ```ruby queue = SizedQueue.new(10) queue.concat(6.times.to_a) queue.concat(6.times.to_a) # Block until there is 6 free slots? ``` I think the simplest would be to wait for enough space to append the entire set, because combined with a timeout, it would be awkward if only part of the array was concatenated. ### Batch pop Similarly, sometimes the consumer of a queue is capable of batching, and right now it's not efficient: ```ruby loop do items = [queue.pop] begin 99.times do items << queue.pop(true) # true is for nonblock end rescue ThreadError # empty queue end process_items(items) end ``` It would be much more efficient if `pop` accepted a `count` parameter: ```ruby loop do items = queue.pop(count: 100) process_items(items) end ``` The behavior would be: - Block if the queue is empty - If it's not empty, return **up to** `count` items (Just like `Array#pop`) ### Non blocking mode, without exception As shown above, the current `nonblock` parameter is a bit awkward, because: - It raises an exception, which is very expensive for a construct often used in "low level" code. - The exception is `ThreadError`, so you may have to match the error message for `"queue empty"`, to make sure it doesn't come from a Mutex issue or something like that. I believe that we could introduce a keyword argument: ```ruby Queue.new.pop(nonblock: true) # => nil ``` -- https://bugs.ruby-lang.org/ Unsubscribe: