开始我们的Semaphore项目时,我给我的学生一个p()方法的错误版本:
proc p() {
while (this.tokens <= 0) {
sleep(1);
writeln("Tokens: ", this.tokens);
}
this.tokens -= 1;
}
我给他们提供了一些额外的代码来测试它,该代码增加了另一个线程中标记的数量(使用v()方法)。您可以看到 token 数量在增加(大于0),但是代码没有退出while循环。
一时兴起,我添加了一个赋值语句:
proc p() {
while (this.tokens <= 0) {
this.tokens = this.tokens;
sleep(1);
writeln("Tokens: ", this.tokens);
}
this.tokens -= 1;
}
这解决了测试运行中的问题(尽管线程安全性更低)。为什么原始的while循环会卡住,为什么添加此分配可以解决该问题?
最佳答案
假设从不同的任务调用了p()
和v()
,则不能保证在一个任务中对非原子this.tokens
的写入将被另一个任务看到。
一种解决方案是使tokens
原子化,并具有以下内容:
proc p() {
while (this.tokens.read() <= 0) {
sleep(1);
writeln("Tokens: ", this.tokens.read());
}
this.tokens.sub(1);
}
当
tokens
不是原子的时,编译器可以自由地假定tokens
不会被其他任务修改,因此它可能会将您的代码转换为以下形式:var tokenTemp = this.token;
while (tokenTemp <= 0)
...
并将写入写入
token
可以防止这种情况。也就是说,即使写入token
,它仍然是无效/非法/未定义的代码,将来很容易被某些编译器/处理器重新排序而跳闸。该代码是非法的,因为它违反了Chapel的内存一致性模型(MCM)。具体来说,这是一场数据竞赛,Chapel仅确保无数据争用程序的顺序一致性。
内存一致性模型在语言规范中定义(https://chapel-lang.org/docs/1.16/_downloads/chapelLanguageSpec.pdf中的第30章)。它有一个很好并且很容易理解的介绍段落。就是说,由于这是语言规范,因此本章的其余部分非常枯燥而又技术性强,因此它可能不是开发人员学习的最佳场所。
有关简短的概述,请查看https://chapel-lang.org/CHIUW/2015/hot-topics/01-Ferguson.pdf(尤其是幻灯片10)。
除此之外,Chapel的内存模型基于C11/C++ 11,Java,UPC等。如果您要寻找“C++ 11内存模型”,“无数据争用程序”或“顺序一致性”,那么这里有很多很棒的文章。
关于chapel - 方法中的while循环卡住了。向其自身添加字段分配可解决此问题,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/48713705/