c++ c++11 unique-ptr lock-free atomic-swap

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

  1. Recuerde global puntero global actual en temp
  2. Guarde local a global si global aún es igual a la temp (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.