Assuming p() and v() called from different tasks, there is no guarantee that writing to the non- this.tokens in one task will ever be considered by another 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 not atomic, the compiler may assume that tokens will not be modified by other tasks, so it will probably convert your code to something like:
var tokenTemp = this.token; while (tokenTemp <= 0) ...
and inserting a record in token prevents the ascent. However, even with token writing, this is still invalid / illegal / undefined code and can easily be handled by some reordering of the compiler / processor in the future.
This code is illegal because it violates the clock memory consistency model (MCM). In particular, it is a data race, and Chapel provides only consistent consistency for race-free programs.
The memory consistency model is defined in the language specification (chapter 30 at https://chapel-lang.org/docs/1.16/_downloads/chapelLanguageSpec.pdf ). It has a nice and fairly affordable introduction paragraph. However, since this is a language specification, the rest of the chapter is pretty dry and technical, so this is probably not the best place for developers to learn.
For a shorter review, take a look at https://chapel-lang.org/CHIUW/2015/hot-topics/01-Ferguson.pdf (especially slide 10).
In addition, the Chapel memory model is based on C11 / C ++ 11, Java, UPC and others. There are plenty of great and affordable articles there if you're looking for a โC ++ 11 memory model,โ โa free data feed program,โ or โconsistent consistency.โ