Пока цикл в методе застревает. Добавление назначения поля самому себе устраняет проблему

Начиная наш проект 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 застревает и почему добавление этого присваивания решает его?


person Kyle    schedule 09.02.2018    source источник


Ответы (1)


Предполагая, что p() и v() вызываются из разных задач, нет никакой гарантии, что запись в неатомарную this.tokens в одной задаче когда-либо будет видна другой задаче.

Одним из решений было бы сделать 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 обеспечивает последовательную согласованность только для программ, свободных от гонок данных.

Модель согласованности памяти определена в спецификации языка (глава 30 в https://chapel-lang.org/docs/1.16/_downloads/chapelLanguageSpec.pdf). У него хороший и довольно доступный вводный абзац. Тем не менее, поскольку это спецификация языка, остальная часть главы довольно сухая и техническая, так что это, вероятно, не лучшее место для обучения разработчиков.

Более краткий обзор см. на https://chapel-lang.org/CHIUW/2015/hot-topics/01-Ferguson.pdf (особенно слайд 10).

Кроме того, модель памяти Chapel основана на C11/C++11, Java, UPC и других. Существует множество отличных и доступных статей, если вы ищете «модель памяти C++ 11», «программу без гонки данных» или «последовательную согласованность».

person Elliot    schedule 09.02.2018
comment
Значение, возвращаемое для каждого вызова this.tokens.read(), может отличаться между проверкой условия while и writeln, верно? - person Lydia Duncan; 10.02.2018
comment
Спасибо, Эллиот! Что делает код недействительным/незаконным/неопределенным? Есть ли что-нибудь, что я мог бы прочитать, чтобы узнать больше об этом? Это звучит так, как будто каждый раз, когда у меня есть объект, который должен быть потокобезопасным, поля должны быть объявлены как атомарные. Это правильно? - person Kyle; 10.02.2018
comment
Это гонка данных между читателем и писателем. Модель согласованности памяти Chapel (основанная на C11/C++11/Java и других) гарантирует последовательную согласованность только для программ без гонок данных. В первом абзаце главы 30 спецификации есть хорошее введение chapel-lang.org /docs/1.16/_downloads/chapelLanguageSpec.pdf и chapel-lang.org/CHIUW/2015/hot-topics/01-Ferguson.pdf содержит некоторые рассуждения в виде слайдов. Вы, вероятно, можете найти несколько замечательных и доступных объяснений, выполнив поиск по запросу «свободная от гонки данных/последовательная согласованность» или модель памяти C++11. - person Elliot; 10.02.2018
comment
@Elliot, я не просматривал все связанные вещи, но мне было интересно, есть ли у вас логический ответ на часть моего комментария о полях, которые должны быть атомарными, чтобы объект был потокобезопасным. - person Kyle; 26.02.2018
comment
Я не думаю, что есть простой логический ответ. Но да, если вы манипулируете числовым полем класса и читаете его из нескольких задач, сделать поле атомарным — это один из способов сделать его потокобезопасным. - person Elliot; 28.02.2018