同期バリアを実装しましょう。処理するスレッドの数nを事前に知っている必要があります。最初のn-1の間にバリアを呼び出すsync
と、呼び出し元のスレッドが待機します。呼び出し番号nは、すべてのスレッドをウェイクアップします。
class Barrier
def initialize(count)
@mutex = Mutex.new
@cond = ConditionVariable.new
@count = count
end
def sync
@mutex.synchronize do
@count -= 1
if @count > 0
@cond.wait @mutex
else
@cond.broadcast
end
end
end
end
の全体sync
がクリティカルセクションです。つまり、2つのスレッドで同時に実行することはできません。したがって、への呼び出しMutex#synchronize
。
の減少値@count
が正の場合、スレッドはフリーズします。ConditionVariable#wait
デッドロックを防ぐには、呼び出しの引数としてミューテックスを渡すことが重要です。これにより、スレッドをフリーズする前にミューテックスのロックが解除されます。
簡単な実験では、1kスレッドを開始し、それらに要素を配列に追加させます。最初にゼロを追加し、次に同期して1を追加します。期待される結果は、2k個の要素を持つソートされた配列であり、そのうち1kはゼロ、1kは1です。
mtx = Mutex.new
arr = []
num = 1000
barrier = Barrier.new num
num.times.map do
Thread.start do
mtx.synchronize { arr << 0 }
barrier.sync
mtx.synchronize { arr << 1 }
end
end .map &:join;
# Prints true. See it break by deleting `barrier.sync`.
puts [
arr.sort == arr,
arr.count == 2 * num,
arr.count(&:zero?) == num,
arr.uniq == [0, 1],
].all?
実際のところ、私が上で説明したことを正確に実行するバリアという名前の宝石があります。
最後に、このような状況で待機するためにスリープを使用しないでください。これはビジーウェイトと呼ばれ、悪い習慣と見なされます。