Обмен двух unique_ptr
s не гарантированно является потокобезопасным.
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
}
Какой идиоматический способ сделать это в C ++ 11?
Кажется, нет общего решения без блокировки для этой проблемы. Для этого вам нужна возможность атомарно записывать новые значения в две неконтинуальные области памяти. Это называется 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;
}
Увидеть Джонатана ответ для еще более короткого и элегантного решения.
В любом случае вам придется написать свой умный указатель. Вы не можете использовать этот трюк со стандартным unique_ptr
,
Идиоматический способ атомарного изменения двух переменных — использовать блокировку.
Вы не можете сделать это для 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
потому что это не может быть реализовано, потому что, как говорит ответ Стаса, это невозможно с большинством процессоров. Стандарт C ++ не имеет привычки стандартизировать функциональность, которая невозможна на всех современных архитектурах. (В любом случае не намеренно!)
Изменить: Ваш обновленный вопрос спрашивает об очень другой проблеме, во втором примере вам не нужен атомный своп, который влияет на два объекта. Только global
распределяется между потоками, поэтому вам все равно, будут ли обновляться local
атомарный, вам просто нужно атомарно обновить global
и получить старое значение. Канонический C ++ 11 способ сделать это с std:atomic<T*>
и вам даже не нужна вторая переменная:
atomic<T*> global;
void f() {
delete global.exchange(new T(...));
}
Это один чтение-модификация-запись операция.