5

Starting our Semaphore project, I gave my students a bad version of the p() method:

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

I give them some additional code to test it which increments the number of tokens (using the v() method) in another thread. You can see the number of tokens increasing (above 0) but the code does not exit the while loop.

On a whim, I added an assignment statement:

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

This solves the problem in the test runs (though it's even less thread-safe). Why does the original while loop get stuck and why does adding this assignment solve it?

Kyle
  • 554
  • 3
  • 10
  • 1
    If you are who I think you are, you may have something to contribute [here](https://cseducators.stackexchange.com/questions/3646/could-you-recommend-books-on-concurrent-programming). (And in the CSE community in general!) – Ben I. May 29 '18 at 18:58
  • @Ben I: The last concurrent-programming textbook I used was Tony Hoare's CSP. – Kyle Jun 18 '18 at 13:23
  • 1
    You should post an answer in CSEd so that it will help future educators as well :) – Ben I. Jun 18 '18 at 13:51
  • I was trying to say that I don't really know about good concurrent texts. – Kyle Jan 30 '19 at 23:17

1 Answers1

4

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"

Elliot
  • 381
  • 1
  • 4
  • 2
    The value returned for each this.tokens.read() call could be different between the while condition check and the writeln, correct? – Lydia Duncan Feb 09 '18 at 21:51
  • Thanks, Elliot! What makes the code "invalid/illegal/undefined"? Is there something I could read to learn more about that? This sounds like anytime I have an object that needs to be thread safe, the fields need to be declared as atomic. Is that correct? – Kyle Feb 10 '18 at 17:45
  • 1
    It's a data race between a reader and writer. Chapel's memory consistency model (based on C11/C++11/Java, and others) only guarantees sequential consistency for data-race-free programs. The first paragraph of chapter 30 in the spec has a good intro https://chapel-lang.org/docs/1.16/_downloads/chapelLanguageSpec.pdf, and https://chapel-lang.org/CHIUW/2015/hot-topics/01-Ferguson.pdf has some of the reasoning in slide form. You can probably find some great and accessible explanations by searching for "data race free/sequential consistency" or "C++11 memory model". – Elliot Feb 10 '18 at 20:18
  • @Elliot, I haven't looked through all the linked things, but I was wondering whether you had a boolean answer to part of my comment about fields needing to be atomic in order for the object to be thread safe. – Kyle Feb 26 '18 at 20:16
  • 1
    I don't think there's a simple boolean answer. But yes, if you're manipulating and reading a numeric class field from multiple tasks, making the field atomic is one way to make it "thread safe". – Elliot Feb 27 '18 at 21:12