开始我们的Semaphore项目,我给了我的学生一个糟糕的p()方法版本:
proc p() {
while (this.tokens <= 0) {
sleep(1);
writeln("Tokens: ", this.tokens);
}
this.tokens -= 1;
}
Run Code Online (Sandbox Code Playgroud)
我给它们一些额外的代码来测试它,它增加了另一个线程中的令牌数量(使用v()方法).您可以看到令牌数量增加(大于0)但代码不会退出while循环.
一时兴起,我添加了一个任务声明:
proc p() {
while (this.tokens <= 0) {
this.tokens = this.tokens;
sleep(1);
writeln("Tokens: ", this.tokens);
}
this.tokens -= 1;
}
Run Code Online (Sandbox Code Playgroud)
这解决了测试运行中的问题(尽管它的线程安全性更低).为什么原始的while循环卡住了,为什么添加这个赋值可以解决它?
假设从不同的任务调用p()和v(),则不能保证一个任务中对非原子的写入this.tokens会被另一任务看到。
一种解决方案是使tokens原子化并具有如下内容:
proc p() {
while (this.tokens.read() <= 0) {
sleep(1);
writeln("Tokens: ", this.tokens.read());
}
this.tokens.sub(1);
}
Run Code Online (Sandbox Code Playgroud)
当tokens不是原子性时,编译器可以自由地假设它tokens不会被其他任务修改,因此它可能会将您的代码转换为类似以下内容的代码:
var tokenTemp = this.token;
while (tokenTemp <= 0)
...
Run Code Online (Sandbox Code Playgroud)
并插入写入以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 内存模型”、“无数据竞争程序”或“顺序一致性”,这里有许多精彩且易于理解的文章
| 归档时间: |
|
| 查看次数: |
124 次 |
| 最近记录: |