Какой тип atomic::Ordering необходимо использовать в данных ситуациях?

Я хочу убедиться, что правильно понимаю порядки, поэтому прошу вас поделиться своим мнением о нескольких ситуациях, описанных ниже: 

1. **Once**: В случае когда необходимо реализовать Once с двумя значениями (WasNotCalled = 0, WasCalled = 1), метод call может использовать swap для возврата предшествующего значения. Какой порядок следует использовать в этом swap?

2. **SmartMutex**: Если у нас имеется очередь ожидающих задач, мы можем записать длину этого списка в SmartMutex. В случае если длина равна -1, мы можем захватить блокировку. Если же она равна 0, то стоит подождать пару спинов и попробовать снова. Если длина больше 0, мы можем только встать в очередь. В этой связи, необходимо реализовать два метода: 
   - Первый метод пытается захватить лок, при этом не становясь в очередь. Он должен выполнить CAS и проверить возвращаемое значение (если оно больше 0, вернуть None). Какие порядки необходимы для данного CAS?
   - Второй метод увеличивает счётчик на единицу и становится в очередь, если предыдущее значение не равняется -1; в противном случае он возвращает блокировку. Какой порядок следует использовать в этом методе?
  • 22 января 2025 г. 18:54
Ответы на вопрос 2
Понимание порядков атомарных операций очень важно при разработке многопоточных приложений, так как неправильный выбор может привести к ошибкам и некорректному поведению программы. Рассмотрим ваши ситуации подробнее:

### 1. **Once** с использованием `swap` для возврата предшествующего значения

В этом случае вы используете `swap` для возврата предыдущего значения переменной-метки (например, `atomic<int>`), которая указывает, был ли вызван метод (0 – не был, 1 – был). Для операции `swap`, чтобы обеспечить последовательность операций в многопоточном контексте, вам потребуется использовать `atomic::Ordering::Release` для операции писем и `atomic::Ordering::Acquire` для операции чтения. Это гарантирует, что все операции, выполненные до `Release`, будут видны другим потокам после `Acquire`. Однако, в случае `swap`, обычно используется просто атомарная операция с `std::memory_order_seq_cst`, что обеспечивает самую строгую последовательность и может быть предпочтительным вариантом для простоты:

```cpp
value.store(1, std::memory_order_release); // Запись состояния
previous_value = value.exchange(1, std::memory_order_acquire); // Обмен значениями
```

### 2. **SmartMutex**

#### Первый метод (CAS для захвата лок)

В этом методе вам нужно использовать операцию CAS (compare-and-swap) для проверки длины очереди и захвата лок. Если длина больше 0, то метод должен завершаться, не становясь в очередь. 

- Для операции CAS используйте `std::memory_order_acquire` для чтения текущего значения и `std::memory_order_release` для записи нового значения (если запись успешна). Это гарантирует, что все изменения, сделанные в предыдущих потоках, будут видны, если захват лок был успешен.

Пример кода:
```cpp
if (len.load(std::memory_order_acquire) == 0 &&
    len.compare_exchange_strong(expected_value, new_value, std::memory_order_release)) {
    // Успешный захват лок
}
```

#### Второй метод (увеличение счётчика и состояние в очередь)

Здесь увеличивается счётчик, и вам нужно удостовериться, что вы корректно обновляете состояние из-за возможного доступа к очереди, когда длина меньше или равна нулю. 

- В этом случае также используйте `std::memory_order_acquire` при чтении длины очереди и `std::memory_order_release` при записи. Это позволит вам гарантировать согласованность данных для других потоков, которые могут пытаться прочитать информацию о длине.

Пример:
```cpp
int old_length = len.load(std::memory_order_acquire);
if (old_length != -1) {
    len.fetch_add(1, std::memory_order_release);
    // Стать в очередь
}
```

Таким образом, правильный выбор порядков может варьироваться в зависимости от специфики вашей системы и контекста, но использованные здесь `acquire` и `release` как правило будут обеспечить необходимую согласованность в большинстве случаев.
Я ничего не знаю про rust, но это же вопрос про memory model, а эта тема уже обсосана C и С++. Например: eel.is/c++draft/atomics.order Самый строгий вариант ордеринга (sequential consistency) будет работать корректно всегда, и если вопрос не стоит как "насколько его можно ослабить", то разумно использовать его. Кроме того, для CAS это же единственная возможная опция, наксколько мне известно.
Похожие вопросы