虽然方法中的循环卡住了.向自身添加字段分配可以解决问题

Kyl*_*yle 5 chapel

开始我们的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循环卡住了,为什么添加这个赋值可以解决它?

Ell*_*iot 4

假设从不同的任务调用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 内存模型”、“无数据竞争程序”或“顺序一致性”,这里有许多精彩且易于理解的文章

  • 每次 this.tokens.read() 调用返回的值在 while 条件检查和 writeln 之间可能不同,对吗? (2认同)