Queue を使えば簡単です。また SizedQueue でもできます。
シンプルに
標準添付ライブラリのQueueを使えばこれらの処理がサクッと実装できちゃいます。
require 'thread' locks = Queue.new 2.times { locks.push :lock } Array.new(10) do |i| Thread.new do lock = locks.pop puts i sleep 2 locks.push lock end end.each(&:join)このコードでは10スレッド生成して2スレッドずつ実行されます。
なぜ動く?
Queueはスレッド間通信のために用意されたクラスなので、push/pop操作はatomicです。(自分でMutexを用意する必要が無い。)
また、空のキューを読み出そうとするとスレッドが停止します
っていう素敵な仕様により、waitの実装も必要無くなります。
Rubyで「スレッドの同時実行数を制限する」シンプルな方法 - Qiita
SizedQueueを使うと?
SizedQueueでも、10スレッド生成して2スレッドずつ実行するやりかたを書いてみました。
locks = SizedQueue.new(2) Array.new(10) do |i| Thread.new do locks.push(:lock) puts i sleep 2 locks.pop end end.each(&:join)
QueueとSizedQueueでは考えが逆になる
Queue の場合には予め Queue#push で同時実行数分を入れておき、実行したいスレッドが Queue#pop で取り出します。Queueが無くなるとスレッドが停止します。
- Queue#pop:キューからひとつ値を取り出します。キューが空の時、呼出元のスレッドは停止します。
- Queue#push:キューの値を追加します。待っているスレッドがいれば実行を再開させます。
他方、SizedQueue の場合は上限を SizedQueue.new の引数に同時実行数を指定し、実行したいスレッドが SizedQueue#push で登録します。上限に達するとスレッドが停止します。
- SizedQueue#pop:キューからひとつ値を取り出します。 キューに push しようと待っているスレッドがあれば、実行を再開させます。
- SizedQueue#push:キューに与えられたオブジェクトを追加します。サイズが SizedQueue#max に達している場合は、 キューのサイズが SizedQueue#max より小さくなるまで他のスレッドに実行を譲ります。 その後、キューに与えられたオブジェクトを追加します。