我需要创建3个线程。 每个线程将在屏幕上打印一个颜色,并休眠x秒。 线程A将打印为红色;线程B将显示黄色;线程C将显示为绿色;
所有线程必须等待,直到轮到他们打印为止。 要打印的第一个线程必须是红色,在打印后,红色将告诉黄色该轮到该打印了,依此类推。 线程必须能够多次打印(特定于用户)
我陷入困境是因为在线程外调用@ firstFlag.signal无法正常工作,并且3个线程的工作顺序不正确 我该如何使红色线程优先?
到目前为止,我的代码:@lock = Mutex.new
@firstFlag = ConditionVariable.new
@secondFlag = ConditionVariable.new
@thirdFlag = ConditionVariable.new
print "Tell me n's vallue:"
@n = gets.to_i
@threads = Array.new
@threads << Thread.new() {
t = Random.rand(1..3)
n = 0
@lock.synchronize {
for i in 0...@n do
@firstFlag.wait(@lock, t)
puts "red : #{t}s"
sleep(t)
@secondFlag.signal
end
}
}
@threads << Thread.new() {
t = Random.rand(1..3)
n = 0
@lock.synchronize {
for i in 0...@n do
@secondFlag.wait(@lock, t)
puts "yellow : #{t}s"
sleep(t)
@thirdFlag.signal
end
}
}
@threads << Thread.new() {
t = Random.rand(1..3)
n = 0
@lock.synchronize {
for i in 0...@n do
@thirdFlag.wait(@lock, t)
puts "green : #{t}s"
sleep(t)
@firstFlag.signal
end
}
}
@threads.each {|t| t.join}
@firstFlag.signal
答案 0 :(得分:2)
您的代码中存在三个错误:
第一个错误
您的wait
呼叫使用超时。这意味着您的线程将与预期的序列不同步,因为超时将使每个线程滑过预期的等待点。
解决方案:将所有等待呼叫更改为不使用超时:
@xxxxFlag.wait(@lock)
第二个错误
您在Thread.join
调用结束后放置了序列触发器。您的join
调用将永远不会返回,因此代码中的最后一条语句将永远不会执行,线程序列也将不会开始。
解决方案:更改顺序以先发出信号,然后再加入线程:
@firstFlag.signal
@threads.each {|t| t.join}
第三个错误
wait/signal
构造的问题在于它不缓冲信号。
因此,您必须在调用wait
之前确保所有线程都处于其signal
状态,否则您可能会遇到竞争状态,其中一个线程在另一个线程调用signal
之前调用wait
。 。
解决方案:尽管可以用Queue
解决,但是这很难解决。但是,我建议对您的代码进行彻底的重新思考。完整的解决方案请参见下文。
更好的解决方案
我认为您需要重新考虑整个结构,而不是使用条件变量,而应将Queue
用于所有内容。现在,代码变得不那么脆弱了,并且由于Queue
本身是线程安全的,因此您不再需要任何关键部分。
Queue
的优点是您可以像wait/signal
那样使用它,但是它可以缓冲信号,在这种情况下,一切都变得更加简单。
现在我们可以重写代码:
redq = Queue.new
yellowq = Queue.new
greenq = Queue.new
然后每个线程变成这样:
@threads << Thread.new() {
t = Random.rand(1..3)
n = 0
for i in 0...@n do
redq.pop
puts "red : #{t}s"
sleep(t)
yellowq.push(1)
end
}
最后要开始整个过程:
redq.push(1)
@threads.each { |t| t.join }
答案 1 :(得分:1)
我会稍微重新设计一下。将您的ConditionVariable
视为标志,线程使用它来表示它已经完成,并相应地命名它们:
@lock = Mutex.new
@thread_a_done = ConditionVariable.new
@thread_b_done = ConditionVariable.new
@thread_c_done = ConditionVariable.new
现在,线程A通过完成@thread_a_done.signal
发出信号,线程B可以等待该信号,依此类推。线程A当然需要等待直到线程C完成,因此我们得到了这种结构:
@threads << Thread.new() {
t = Random.rand(1..3)
@lock.synchronize {
for i in 0...@n do
@thread_c_done.wait(@lock)
puts "A: red : #{t}s"
sleep(t)
@thread_a_done.signal
end
}
}
这里的问题是,您需要确保第一次迭代中的线程A不等待标志信号。毕竟,它是第一位的,因此它不应该等待其他任何人。因此,将其修改为:
@thread_c_done.wait(@lock) unless i == 0
最后,创建线程后,先调用run
,然后在每个线程上join
将它们全部踢开(这样程序就不会在最后一个线程完成之前退出):
@threads.each(&:run)
@threads.each(&:join)
哦,顺便说一句,我也会摆脱您的wait
中的超时问题。您有一个严格的要求,要求他们按顺序进行。如果使信号等待超时,则可能会导致超时-可以这么说,线程可能仍会“跳过队列”。
编辑,如下所示,@ casper仍然有潜在的竞争条件:线程A可以在线程B等待接收之前调用signal
在这种情况下,线程B会错过它并无限期地等待。解决此问题的一种可能方法是使用某种形式的CountDownLatch
-所有线程都可以等待的共享对象,一旦所有线程都表示它们已准备就绪,该对象将被释放。 ruby-concurrency gem具有此实现,并且实际上可能还有其他有趣的东西可用于更优雅的多线程编程。
不过,使用纯红宝石进行标记,您可以通过添加第二个Mutex
来解决此问题,该while(in7.hasNextLine()) {
for (int sl = 0; sl < list2.length; sl++) {
String[] line = in7.nextLine().trim().split(" ");
list2[sl] = Double.parseDouble(line);
}
}
可以保护对布尔标志的共享访问,以指示线程已准备就绪。
答案 2 :(得分:0)
好的,谢谢回答。我找到了解决方法:
我创建了第四个线程。因为我发现在线程外调用“ @ firstFlag.signal”是行不通的,因为ruby有一个“主线程”,当您“运行”其他线程时,它会休眠。 因此,“ @ firstFlag.signal”调用必须在线程内,以便可以在CV.wait的同一级别上。
我使用以下方法解决了这个问题:
@threads << Thread.new {
sleep 1
@firstFlag.signal
}
第四个线程将等待1秒钟,然后将第一个信号发送为红色。这仅几秒钟似乎足以使其他线程到达等待点。 而且,正如您的要求一样,我已删除超时。
//编辑//
我意识到我不需要第四个线程,我可以让线程C做第一个信号。 我使线程C睡眠1秒钟,以等待其他两个线程进入等待状态,然后它发出红色信号,也开始等待。
@threads << Thread.new() {
sleep 1
@redFlag.signal
t = Random.rand(1..3)
n = 0
@lock.synchronize {
for i in 0...@n do
@greenFlag.wait(@lock)
puts "verde : #{t}s"
sleep(t)
@redFlag.signal
n += 1
end
}
}