Подтвердить что ты не робот

Блокировка без замены двух уникальных_ptr <T>

Подкачка двух unique_ptr не гарантируется как потокобезопасная.

std::unique_ptr<T> a, b;
std::swap(a, b); // not threadsafe

Так как мне нужны свопы с атомарным указателем, и поскольку мне нравится управление владельцем unique_ptr, есть ли простой способ их комбинировать?


Изменить: если это невозможно, я открыт для альтернатив. Я хотя бы хочу сделать что-то вроде этого:

threadshared_unique_ptr<T> global;

void f() {
   threadlocal_unique_ptr<T> local(new T(...));
   local.swap_content(global); // atomically for global
}

Каков идиоматический способ сделать это в С++ 11?

4b9b3361

Ответ 1

Блокировка без двух указателей

Похоже, для этой проблемы нет общего безопасного решения. Для этого вам нужна возможность атомарно записывать новые значения в две непересекающиеся ячейки памяти. Это называется DCAS, но он недоступен в процессорах Intel.

Запрет на передачу права собственности

Это возможно, так как это необходимо только для автоматического сохранения нового значения в global и получения его старого значения. Моя первая идея заключалась в том, чтобы использовать CAS. Взгляните на следующий код, чтобы получить представление:

std::atomic<T*> global;

void f() {
   T* local = new T;
   T* temp = nullptr;
   do {
       temp = global;                                                   // 1
   } while(!std::atomic_compare_exchange_weak(&global, &temp, local));  // 2

   delete temp;
}

Шаги

  • Помните текущий указатель global в temp
  • Сохранить local до global, если global по-прежнему равно temp (он не был изменен другим потоком). Повторите попытку, если это неверно.

На самом деле, CAS здесь избыток, так как мы не делаем ничего особенного со старым значением global до его изменения. Итак, мы можем использовать операцию атомного обмена:

std::atomic<T*> global;

void f() {
   T* local = new T;
   T* temp = std::atomic_exchange(&global, local);
   delete temp;
}

Подробнее см. Jonathan для более короткого и элегантного решения.

В любом случае вам придется написать свой собственный умный указатель. Вы не можете использовать этот трюк со стандартным unique_ptr.

Ответ 2

Идиоматическим способом атомарного изменения двух переменных является использование блокировки.

Вы не можете сделать это для std::unique_ptr без блокировки. Даже std::atomic<int> не дает возможности поменять местами два значения атомарно. Вы можете обновить один атомарно и вернуть свое предыдущее значение, но своп концептуально три шага, с точки зрения std::atomic API:

auto tmp = a.load();
tmp = b.exchange(tmp);
a.store(tmp);

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

Для не скопируемого значения, такого как std::unique_ptr<T>, вы даже не можете использовать операции load и store выше, но должны:

auto tmp = a.exchange(nullptr);
tmp = b.exchange(tmp);
a.exchange(tmp);

Это три операции чтения-изменения-записи. (Вы действительно не можете использовать std::atomic<std::unique_ptr<T>> для этого, потому что для этого требуется тип с возможностью копирования с возможностью копирования, а std::unique_ptr<T> не является каким-либо копируемым.)

Для этого с меньшим количеством операций потребуется другой API, который не поддерживается std::atomic, потому что он не может быть реализован, потому что, как говорит Stas, это невозможно для большинства процессоров. Стандарт С++ не имеет привычки стандартизировать функциональность, которая невозможна на всех современных архитектурах. (Не намеренно в любом случае!)

Изменить: ваш обновленный вопрос задает совершенно другую проблему, во втором примере вам не нужен атомный своп, который затрагивает два объекта. Только поток global разделяется между потоками, поэтому вам не важно, являются ли обновления для local атомарными, вам просто нужно атомизировать обновление global и получить старое значение. Канонический С++ 11 способ сделать это с помощью std:atomic<T*>, и вам даже не нужна вторая переменная:

atomic<T*> global;

void f() {
   delete global.exchange(new T(...));
}

Это одна операция чтения-изменения-записи.