我的问题是我不知道如何使用Ruby同步多个线程。任务是创建六个线程并立即启动它们。所有这些都应该按照我需要它的顺序一个接一个地完成一些工作(例如puts "Thread 1" Hi"
)。
经过一段时间与Mutex和条件变量的斗争后,我实现了我的目标。 这段代码有点乱,我故意不使用循环来“更清晰地看”。
cv = ConditionVariable.new
mutex = Mutex.new
mutex2 = Mutex.new
cv2 = ConditionVariable.new
mutex3 = Mutex.new
cv3 = ConditionVariable.new
mutex4 = Mutex.new
cv4 = ConditionVariable.new
mutex5 = Mutex.new
cv5 = ConditionVariable.new
mutex6 = Mutex.new
cv6 = ConditionVariable.new
Thread.new do
mutex.synchronize {
puts 'First: Hi'
cv.wait(mutex)
puts 'First: Bye'
#cv.wait(mutex)
cv.signal
puts 'First: One more time'
}
end
Thread.new do
mutex.synchronize {
puts 'Second: Hi'
cv.signal
cv.wait(mutex)
puts 'Second:Bye'
cv.signal
}
mutex2.synchronize {
puts 'Second: Starting third'
cv2.signal
}
end
Thread.new do
mutex2.synchronize {
cv2.wait(mutex2)
puts 'Third: Hi'
}
mutex3.synchronize {
puts 'Third: Starting forth'
cv3.signal
}
end
Thread.new do
mutex3.synchronize {
cv3.wait(mutex3)
puts 'Forth: Hi'
}
mutex4.synchronize {
puts 'Forth: Starting fifth'
cv4.signal
}
end
Thread.new do
mutex4.synchronize {
cv4.wait(mutex4)
puts 'Fifth: Hi'
}
mutex5.synchronize {
puts 'Fifth: Starting sixth'
cv5.signal
}
end
Thread.new {
mutex5.synchronize {
cv5.wait(mutex5)
puts 'Sixth:Hi'
}
}
sleep 2
答案 0 :(得分:5)
您可以滥用Queue,像传统的PV Semaphore一样使用它。为此,您需要创建一个Queue实例:
require 'thread'
...
sem = Queue.new
当线程需要等待时,它会调用Queue#deq:
# waiting thread
sem.deq
当某个其他线程想要解除阻塞等待线程时,它会将某些内容(任何内容)推送到队列中:
# another thread that wants to unblock the waiting thread
sem.enq :go
这是一个使用Queue同步其开始和结束的工人类:
class Worker
def initialize(worker_number)
@start = Queue.new
Thread.new do
@start.deq
puts "Thread #{worker_number}"
@when_done.call
end
end
def start
@start.enq :start
end
def when_done(&block)
@when_done = block
end
end
构造时,工作者创建一个线程,然后该线程在@start
队列上等待。直到#start被调用,线程才会解锁。
完成后,线程将执行调用#when_done的块。我们将在短时间内看到它是如何使用的。
首先,让我们确保如果任何线程引发异常,我们就会发现它:
Thread.abort_on_exception = true
我们需要六名工人:
workers = (1..6).map { |i| Worker.new(i) }
这是#when_done发挥作用的地方:
workers.each_cons(2) do |w1, w2|
w1.when_done { w2.start }
end
这依次需要每一对工人。每个工人除了最后一个被告知,当它完成时,它应该在它之后启动工人。那就是最后一个工人。完成后,我们希望它通知此线程:
all_done = Queue.new
workers.last.when_done { all_done.enq :done }
现在剩下的就是启动第一个线程:
workers.first.start
并等待最后一个帖子完成:
all_done.deq
输出:
Thread 1
Thread 2
Thread 3
Thread 4
Thread 5
Thread 6
答案 1 :(得分:0)
您可以为每个数字分配一个数字,该数字将表示它在队列中的位置,然后检查它以查看它的轮次:
class QueuedWorker
def initialize(mutex, condition_variable, my_turn)
@mutex = mutex
@my_turn = my_turn
@condition_variable = condition_variable
end
def self.turn
@turn ||= 0
end
def self.done
@turn = turn + 1
end
def run
loop do
@mutex.synchronize do
if QueuedWorker.turn == @my_turn
# do actual work
QueuedWorker.done
@condition_variable.signal
return
end
@condition_variable.signal
@condition_variable.wait(@mutex)
end
end
end
end
mutex = Mutex.new
cv = ConditionVariable.new
(0..10).each do |i|
Thread.new do
QueueWorker.new(mutex, cv, i).run
end
end
话虽这么说,实现很尴尬,因为线程专门为串行工作而不是 。如果你需要连续工作的东西,可以在一个线程中完成。
答案 2 :(得分:0)
如果你刚开始使用线程,你可能想尝试一些简单的事情。让第一个线程休眠1秒,第二个线程休眠2秒,第三个线程休眠3秒,依此类推:
$stdout.sync = true
threads = []
(1..6).each do |i|
threads << Thread.new {
sleep i
puts "Hi from thread #{i}"
}
end
threads.each(&:join)
输出(由于线程并行运行需要6秒):
Hi from thread 1
Hi from thread 2
Hi from thread 3
Hi from thread 4
Hi from thread 5
Hi from thread 6