Threadの同時実行数を指定するには

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 より小さくなるまで他のスレッドに実行を譲ります。 その後、キューに与えられたオブジェクトを追加します。