所以基本上这就是我要解决的问题:
<块引用>大卫、肖恩和弗兰克不断地播种。大卫挖洞。然后肖恩 在每个孔中放置一粒种子。弗兰克然后填补了这个漏洞。有几个 同步约束:
除非至少有一个空洞,否则肖恩不能种下种子,但肖恩可以 不在乎大卫领先肖恩多远。
弗兰克不能填补一个洞,除非至少有一个洞,肖恩 种下了一颗种子,但这个洞还没有被填满。弗兰克不在乎如何 肖恩远远领先于弗兰克。
弗兰克确实在意大卫不会提前打出超过 MAX 个洞 坦率。因此,如果有 MAX 个未填充的孔,大卫必须等待。
大卫和弗兰克只需要一把铲子来挖掘和填满 孔,分别。
为代表 David、Sean 和 Frank 的 3 个进程编写伪代码 使用信号量作为同步机制。确保初始化 信号量。
这是我用 Java 实现的解决方案,据我所知,与 standard solution 相比,它不是很优雅,也有点浪费:
import java.util.concurrent.Semaphore;
public class Holes {
private static final int MAX = 3;
private static final Semaphore mutexForShovel = new Semaphore(1, true);
private static final Semaphore mutexForHoleCount = new Semaphore(1, true);
private static int emptyHoleCount = 0, seededHoleCount = 0, finishedHoleCount = 0;
public static void main(String[] args) {
new Thread(() -> { // David
try {
while (true) {
if (emptyHoleCount < MAX) {
mutexForShovel.acquire(); // wait for shovel
System.out.println("David is digging a hole...");
Thread.sleep(200); // try annotating this
mutexForShovel.release(); // release shovel
mutexForHoleCount.acquire(); // enter critical section
emptyHoleCount++;
mutexForHoleCount.release(); // exit critical section
System.out.println("Empty = " + emptyHoleCount +
" Seeded = " + seededHoleCount +
" Finished = " + finishedHoleCount);
}
}
} catch (InterruptedException e) {
System.out.println(e.toString());
}
}).start();
new Thread(() -> { // Sean
while (true) {
try {
if (emptyHoleCount > 0) {
System.out.println("Sean is seeding a hole...");
Thread.sleep(200); // try annotating this
mutexForHoleCount.acquire(); // enter critical section
emptyHoleCount--;
seededHoleCount++;
mutexForHoleCount.release(); // exit critical section
System.out.println("Empty = " + emptyHoleCount +
" Seeded = " + seededHoleCount +
" Finished = " + finishedHoleCount);
}
} catch (InterruptedException e) {
System.out.println(e.toString());
}
}
}).start();
new Thread(() -> { // Frank
while (true) {
try {
if (seededHoleCount > 0) {
mutexForShovel.acquire(); // ask for shovel
System.out.println("Frank is filling a hole...");
Thread.sleep(200); // try annotating this
mutexForShovel.release(); // release shovel
mutexForHoleCount.acquire(); // enter critical section
seededHoleCount--;
finishedHoleCount++;
mutexForHoleCount.release(); // exit critical section
System.out.println("Empty = " + emptyHoleCount +
" Seeded = " + seededHoleCount +
" Finished = " + finishedHoleCount);
}
} catch (InterruptedException e) {
System.out.println(e.toString());
}
}
}).start();
}
}
我认为我对 acquire()
和 release()
操作的排序方式避免了任何“保持等待”,从而避免了死锁。但是,这是我在终端中运行它得到的输出:
David is digging a hole...
Empty = 1 Seeded = 0 Finished = 0
David is digging a hole...
Empty = 2 Seeded = 0 Finished = 0
David is digging a hole...
Empty = 3 Seeded = 0 Finished = 0
我很困惑,我在调试器中一行一行地运行它,这就是我得到的:
David is digging a hole...
Sean is seeding a hole...
Frank is filling a hole...
Empty = 1 Seeded = 0 Finished = 0
Empty = 0 Seeded = 1 Finished = 0
David is digging a hole...
Empty = 1 Seeded = 0 Finished = 1
Sean is seeding a hole...
David is digging a hole...
Empty = 0 Seeded = 0 Finished = 1
Empty = 0 Seeded = 1 Finished = 1
Frank is filling a hole...
Empty = 1 Seeded = 1 Finished = 1
Empty = 1 Seeded = 0 Finished = 2
Sean is seeding a hole...
David is digging a hole...
...
现在这是合理的输出!不是吗?继续,我删除了带注释的 Thread.sleep()
行,我得到了这个:
...
Sean is seeding a hole...
Frank is filling a hole...
Empty = 2 Seeded = 0 Finished = 29748
Empty = 2 Seeded = 1 Finished = 29747
Sean is seeding a hole...
Frank is filling a hole...
Empty = 1 Seeded = 1 Finished = 29748
Empty = 1 Seeded = 0 Finished = 29749
Sean is seeding a hole...
Frank is filling a hole...
Empty = 0 Seeded = 1 Finished = 29749
Empty = 0 Seeded = 0 Finished = 29750
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
还是不错的输出!
所以我想我现在有两个问题:
Q1:终端中的原始输出是否表明发生了死锁?如果是这样,我的代码的哪一部分是错误的,我该如何更改?
问题 2:为什么我以不同的方式运行基本相同的代码却得到不同的输出?
非常感谢!!!
答案 0 :(得分:2)
private static volatile int emptyHoleCount = 0,
seededHoleCount = 0,
finishedHoleCount = 0;
现在再试一次。线程不会注意到这些变量的变化,因为它们没有被标记为 volatile
。
volatile
具有内存可见性的语义。基本上,价值
所有读者都可以看到 volatile 字段的内容(其他线程
特别)在写操作完成之后。 没有挥发性,
读者可以看到一些未更新的值。
volatile 修饰符保证 任何读取字段的线程都将看到最近写入的值。
您的 while 循环可能会受到编译器优化的影响,因此不会检查变量更新。将变量标记为 volatile
可以避免这种情况(就像说,嘿,不要优化这个,它可能会改变,所以不断检查它的值)。
当您使它们休眠或调试它们时,您正在生成一个线程状态更改,这可能涉及您的线程在返回运行状态时再次检查变量的值。如果没有那个“状态改变”,你的线程会读取过时的数据。
除此之外,作为一个建议,您的线程包含一个非常危险的循环:
while(true)
如示例中所示,您可以通过调用 SIGINT
来阻止它们。我建议实施某种监视器或设置一些易失性布尔标志,以便优雅地停止它们。
涉及非易失性变量的死锁示例
public class UntilYouUpdateIt
{
public static boolean flag = true;
public static void main(String[] args) throws InterruptedException
{
Thread t1 = new Thread(()->
{
while(flag){}
System.out.println("end");
});
t1.start();
Thread.sleep(100);
Thread t2 = new Thread(()->
{
flag = false;
System.out.println("changed");
});
t2.start();
}
}
上面的代码会输出:
changed
但该计划永远不会结束。由于编译器优化,线程 t1
永远不会退出其循环,假设 flag
始终为真。将布尔值设置为 volatile
可以避免这种情况,就像您示例的 int 变量一样。
答案 1 :(得分:1)
如果不满足启动条件,您的线程将进入快速流失状态。也就是说,它们变成了“while(true) if (false)”,然后循环。由于没有工作要做,它们旋转得非常快,并且可能会对其他想要使用 CPU 的东西产生负面影响。我建议如果没有工作要做(不满足启动条件),在再次检查之前让线程休眠。这样线程(无需工作)可以与其他线程很好地协作。
您应该查看计算机性能图表。如果您遇到死锁,那么您会期望 CPU 图形全部归零 - 没有 CPU 可以向前移动,因为每个都在等待另一个。我认为您实际上是在达到消耗 - CPU 可能固定在 100% 并保持在那里。无限循环线程占用了应用程序的 CPU,而从循环中解脱出来的线程永远得不到足够的空气来启动。
答案 2 :(得分:-1)
是的,在无法工作时使用 volatile 和一些放松时间,
而且,播种机肖恩不应该emptyHoleCount--;
填充弗兰克应该。
约束 #3 是挖掘者 Dean 和填充者 Frank 之间的合同,挖掘者 Dean 兑现了 if(emptyHoleCount < MAX)
但播种者 Sean 正在干扰,而不是填充者 Frank。