所以基本上这就是我要解决的问题:
大卫、肖恩和弗兰克不停地播种。大卫挖洞。然后肖恩在每个洞里放一粒种子。弗兰克把洞补上。有几个同步约束:
肖恩不能播种,除非至少有一个空洞存在,但肖恩不在乎大卫领先肖恩多远。
弗兰克不能填补一个洞,除非至少有一个洞肖恩种下了种子,但洞还没有填补。弗兰克不在乎肖恩领先弗兰克多远。
弗兰克很在意大卫在弗兰克前面的洞数不能超过最大洞数。因此,如果有最大未填充的孔,大卫必须等待。
只有一把铲子,大卫和弗兰克分别需要用铲子挖洞和填洞。
使用信号量作为同步机制,为代表david、sean和frank的3个进程编写伪代码。确保初始化信号量。
这是我用java实现的解决方案,我知道,与标准解决方案相比,它不是很优雅,也有点浪费:
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)
同样,这也是不错的产出!
所以我想我现在有两个问题:
问题1:终端中的原始输出是否表明发生了死锁?如果是这样的话,我的代码的哪一部分是错误的,我怎样才能更改它?
问题2:为什么我得到三个不同的输出,以三种不同的方式运行基本相同的代码?
谢谢!!!
3条答案
按热度按时间ct3nt3jp1#
如果不满足启动条件,线程将进入快速搅动状态。也就是说,它们变为“while(true)if(false)”,然后循环运行。由于没有工作要做,它们的转速非常快,可能会对其他需要使用cpu的东西产生负面影响。我建议,如果没有工作要做(启动条件不满足),有线程睡眠,然后再检查。这样线程(没有工作要做)就可以很好地处理其他线程。
你应该看电脑性能图表。如果遇到死锁,那么期望cpu图都归零—没有cpu可以向前移动,因为每个cpu都在等待另一个。我认为你实际上是在打击消费-cpu可能钉在100%,并保持在那里。无限循环线程会吸走cpu,而从循环中挣脱出来的线程永远无法获得足够的空气来启动。
3qpi33ja2#
现在再试一次。线程不会注意到这些变量的变化,因为它们没有被标记为
volatile
.volatile
具有内存可见性的语义。基本上,一个volatile字段的值在写操作完成后对所有读卡器(特别是其他线程)都可见。如果没有volatile,读者可以看到一些未更新的值。volatile修饰符保证任何读取字段的线程都会看到最近写入的值。
while循环可能会受到编译器优化的影响,因此无法检查变量更新。将变量标记为
volatile
避免这种情况(就像说,嘿,不要优化这个,它可能会改变,所以不断检查它的值)。当您休眠或调试它们时,您正在生成一个线程状态更改,该更改可能涉及线程在返回到运行状态时再次检查变量的值。如果没有“状态更改”,线程将读取过时的数据。
除此之外,建议您的线程包含一个非常危险的循环:
while(true)
如您在示例中所示,您可以通过SIGINT
打电话。我建议实现某种监视器或设置一些易失性布尔标志,以便优雅地阻止它们。包含非易失性变量的死锁示例
上面的代码将输出:
changed
但这个计划永远不会结束。线程t1
不会因为编译器优化而退出其循环,假设flag
永远都是真的。将布尔值设置为volatile
避免这种情况,就像使用示例的int变量一样。2w2cym1i3#
是的,在不能工作的时候可以利用一些不稳定和放松的时间,但是,播种机肖恩也不应该这样
emptyHoleCount--;
弗兰克应该。约束条件#3是digger dean和filler frank之间的合同,digger dean以该合同为荣
if(emptyHoleCount < MAX)
但那个播种者肖恩在干涉,而不是弗兰克。