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

时间:2018-02-09 20:34:53

标签: chapel

开始我们的Semaphore项目,我给了我的学生一个糟糕的p()方法版本:

proc p() {
    while (this.tokens <= 0) {
        sleep(1);
        writeln("Tokens: ", this.tokens);
    }
    this.tokens -= 1;
}

我给他们一些额外的代码来测试它,它增加了另一个线程中的令牌数量(使用v()方法)。您可以看到令牌数量增加(大于0)但代码不会退出while循环。

一时兴起,我添加了一份作业声明:

proc p() {
    while (this.tokens <= 0) {
        this.tokens = this.tokens;
        sleep(1);
        writeln("Tokens: ", this.tokens);
    }
    this.tokens -= 1;
}

这解决了测试运行中的问题(尽管它的线程安全性更低)。为什么原始的while循环会卡住,为什么添加这个赋值会解决它?

1 个答案:

答案 0 :(得分:4)

假设从不同的任务调用p()v(),则无法保证在一个任务中对非原子this.tokens的写入将被不同的任务看到。< / p>

一种解决方案是使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内存模型”,“数据竞争免费程序”或“顺序一致性”,那里有很多很棒且易于访问的文章