c++ - Intercambio sin bloqueo de dos unique_ptr<T>
c++11 unique-ptr (2)
No se garantiza que el intercambio de dos unique_ptr
s sea seguro para los hilos.
std::unique_ptr<T> a, b;
std::swap(a, b); // not threadsafe
Como necesito swaps de punteros atómicos y dado que me gusta el manejo de la propiedad de unique_ptr
, ¿hay alguna manera simple de combinarlos?
Editar: si esto no es posible, estoy abierto a alternativas. Al menos quiero hacer algo como esto:
threadshared_unique_ptr<T> global;
void f() {
threadlocal_unique_ptr<T> local(new T(...));
local.swap_content(global); // atomically for global
}
¿Cuál es la forma idiomática de hacer esto en C ++ 11?
La forma idiomática de modificar dos variables atómicamente es usar un bloqueo.
No puede hacerlo para std::unique_ptr
sin un bloqueo. Incluso std::atomic<int>
no proporciona una forma de intercambiar dos valores atómicamente. Puede actualizar uno atómicamente y recuperar su valor anterior, pero un intercambio es conceptualmente tres pasos, en términos de la API std::atomic
son:
auto tmp = a.load();
tmp = b.exchange(tmp);
a.store(tmp);
Esta es una lectura atómica seguida de una escritura atómica de lectura-modificación seguida de una escritura atómica. Cada paso se puede hacer atómicamente, pero no se pueden hacer los tres de forma atómica sin un bloqueo.
Para un valor no copiable, como std::unique_ptr<T>
, ni siquiera puede usar las operaciones de load
y store
anteriores, pero debe hacer:
auto tmp = a.exchange(nullptr);
tmp = b.exchange(tmp);
a.exchange(tmp);
Estas son tres operaciones de lectura, modificación y escritura . (Realmente no se puede usar std::atomic<std::unique_ptr<T>>
para hacer eso, porque requiere un tipo de argumento que se puede copiar trivialmente, y std::unique_ptr<T>
no es de ningún tipo que se pueda copiar. )
Para hacerlo con menos operaciones necesitaría una API diferente que no es compatible con std::atomic
porque no se puede implementar porque, como dice la respuesta de Stas, no es posible con la mayoría de los procesadores. El estándar C ++ no tiene el hábito de estandarizar la funcionalidad que es imposible en todas las arquitecturas contemporáneas. (¡No intencionalmente de todos modos!)
Editar: su pregunta actualizada pregunta sobre un problema muy diferente, en el segundo ejemplo, no necesita un intercambio atómico que afecte a dos objetos. Solo global
se comparte entre subprocesos, por lo que no te importa si las actualizaciones local
son atómicas, solo necesitas actualizar atómicamente global
y recuperar el valor anterior. La manera canónica de C ++ 11 de hacer eso es con std:atomic<T*>
y ni siquiera necesita una segunda variable:
atomic<T*> global;
void f() {
delete global.exchange(new T(...));
}
Esta es una operación única de lectura, modificación y escritura .
Intercambio sin bloqueo de dos punteros
Parece que no hay una solución general sin bloqueo para este problema. Para hacer esto, necesita una posibilidad de escribir atómicamente nuevos valores en dos ubicaciones de memoria no continuas. Esto se llama DCAS
, pero no está disponible en los procesadores Intel.
Transferencia de propiedad sin bloqueo
Esto es posible, ya que solo es necesario para guardar atómicamente el nuevo valor en global
y recibir su valor anterior. Mi primera idea fue usar la operación CAS
. Eche un vistazo al siguiente código para tener una idea:
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;
}
Pasos
- Recuerde
global
punteroglobal
actual entemp
- Guarde
local
aglobal
siglobal
aún es igual a latemp
(no fue modificado por otro hilo). Intenta nuevamente si esto no es verdad.
En realidad, CAS
es excesivo allí, ya que no hacemos nada especial con el antiguo valor global
antes de que se modifique. Entonces, solo podemos usar la operación de intercambio atómico:
std::atomic<T*> global;
void f() {
T* local = new T;
T* temp = std::atomic_exchange(&global, local);
delete temp;
}
Vea la respuesta de Jonathan para una solución aún más corta y elegante.
De todos modos, tendrá que escribir su propio puntero inteligente. No puede usar este truco con unique_ptr
estándar.