Assuming p()
and v()
are being called from different tasks, there's no guarantee that writes to the non-atomic this.tokens
in one task will ever be seen by a different task.
One solution would be to make tokens
atomic and have something like:
proc p() {
while (this.tokens.read() <= 0) {
sleep(1);
writeln("Tokens: ", this.tokens.read());
}
this.tokens.sub(1);
}
When tokens
is not atomic, the compiler is free to assume that tokens
won't be modified by other tasks, so it's probably transforming your code into something like:
var tokenTemp = this.token;
while (tokenTemp <= 0)
...
and inserting the write to token
prevents that hoisting. That said, even with the write to token
, it's still invalid/illegal/undefined code and could easily be tripped by some compiler/processor reordering in the future.
The code is illegal because it violates Chapel's Memory Consistency Model (MCM). Specifically, it's a data race, and Chapel only ensures sequential consistency for data-race-free programs.
The Memory Consistency Model is defined in the language spec (chapter 30 in https://chapel-lang.org/docs/1.16/_downloads/chapelLanguageSpec.pdf). It has a good and pretty accessible introduction paragraph. That said, since it's a language specification, the rest of the chapter is pretty dry and technical, so it's probably not the best place for developers to learn from.
For a shorter overview take a look at https://chapel-lang.org/CHIUW/2015/hot-topics/01-Ferguson.pdf (especially slide 10).
Beyond that, Chapel's memory model is based on C11/C++11, Java, UPC, and others. There's a host of great and accessible articles out there if you look for "C++11 memory model", "data race free program", or "sequential consistency"