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