Pregunta

¿Alguien sabe de una implementación shared_ptr totalmente segura para subprocesos? P.ej. impulsar la implementación de shared_ptr es seguro para subprocesos para los destinos (recuento) y también seguro para lecturas de instancia simultáneas de shared_ptr , pero no escrituras o para lectura / escritura.

(consulte Boost docs , ejemplos 3, 4 y 5).

¿Existe una implementación shared_ptr que sea completamente segura para subprocesos para instancias shared_ptr ?

Es extraño que los documentos de impulso digan eso:

  Los

objetos shared_ptr ofrecen el mismo nivel de seguridad de subprocesos que los tipos integrados.

Pero si compara un puntero ordinario (tipo incorporado) con smart_ptr , la escritura simultánea de un puntero ordinario es segura para subprocesos, pero la escritura simultánea en un smart_ptr no lo es.

EDITAR: me refiero a una implementación sin bloqueo en la arquitectura x86.

EDIT2: Un caso de uso de ejemplo para un puntero inteligente de este tipo sería donde hay varios subprocesos de trabajo que actualizan un shared_ptr global con su elemento de trabajo actual y un subproceso de monitor que toma muestras aleatorias de los elementos de trabajo. Shared-ptr sería el propietario del elemento de trabajo hasta que se le asigne otro puntero de elemento de trabajo (destruyendo así el elemento de trabajo anterior). El monitor obtendría la propiedad del elemento de trabajo (evitando así que el elemento de trabajo se destruya) asignándolo a su propio ptr compartido. Se puede hacer con XCHG y eliminación manual, pero sería bueno si un ptr compartido pudiera hacerlo.

Otro ejemplo es donde el ptr global compartido tiene un "procesador", y es asignado por algún hilo, y usado por algún otro hilo. Cuando el " usuario " thread ve que el procesador shard-ptr es NULL, utiliza alguna lógica alternativa para realizar el procesamiento. Si no es NULL, evita que el procesador sea destruido al asignarlo a su propio ptr compartido.

¿Fue útil?

Solución

Agregar las barreras necesarias para una implementación de shared_ptr tan segura para subprocesos probablemente afectaría el rendimiento. Considere la siguiente raza (nota: abunda el pseudocódigo):

Tema 1:     global_ptr = A;

Tema 2:     global_ptr = B;

Tema 3:     local_ptr = global_ptr;

Si dividimos esto en sus operaciones constituyentes:

Tema 1:

A.refcnt++;
tmp_ptr = exchange(global_ptr, A);
if (!--tmp_ptr.refcnt) delete tmp_ptr;

Tema 2:

B.refcnt++;
tmp_ptr = exchange(global_ptr, B);
if (!--tmp_ptr.refcnt) delete tmp_ptr;

Tema 3:

local_ptr = global_ptr;
local_ptr.refcnt++;

Claramente, si el subproceso 3 lee el puntero después del intercambio de A, entonces B va y lo elimina antes de que se pueda incrementar el recuento de referencias, sucederán cosas malas.

Para manejar esto, necesitamos un valor ficticio que se utilizará mientras el hilo 3 está haciendo la actualización refcnt: (nota: compare_exchange (variable, esperado, nuevo) reemplaza atómicamente el valor en variable con nuevo si actualmente es igual a nuevo, luego devuelve verdadero si lo hizo con éxito)

Tema 1:

A.refcnt++;
tmp_ptr = global_ptr;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, A))
    tmp_ptr = global_ptr;
if (!--tmp_ptr.refcnt) delete tmp_ptr;

Tema 2:

B.refcnt++;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, A))
    tmp_ptr = global_ptr;
if (!--tmp_ptr.refcnt) delete tmp_ptr;

Tema 3:

tmp_ptr = global_ptr;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, BAD_PTR))
    tmp_ptr = global_ptr;
local_ptr = tmp_ptr;
local_ptr.refcnt++;
global_ptr = tmp_ptr;

Ahora ha tenido que agregar un bucle, con elementos atómicos en el medio de su / lectura / operación. Esto no es algo bueno, puede ser extremadamente costoso en algunas CPU. Además, estás ocupado, esperando también. Puede comenzar a ser inteligente con futexes y otras cosas, pero en ese punto ya ha reinventado el bloqueo.

Este costo, que debe ser asumido por cada operación, y es muy similar en naturaleza a lo que un bloqueo le daría de todos modos, es la razón por la cual generalmente no ve implementaciones de shared_ptr seguras para subprocesos. Si necesita tal cosa, recomendaría envolver un mutex y shared_ptr en una clase conveniente para automatizar el bloqueo.

Otros consejos

La escritura simultánea en un puntero incorporado ciertamente no es seguro para subprocesos. Considere las implicaciones de escribir con el mismo valor con respecto a las barreras de memoria si realmente quiere volverse loco (por ejemplo, podría tener dos hilos pensando que el mismo puntero tenía valores diferentes).

RE: Comentario: la razón por la que las incorporaciones no se eliminan por duplicado es porque no se eliminan en absoluto (y la implementación de boost :: shared_ptr que uso no eliminaría por duplicado, ya que utiliza un incremento atómico especial y decremento, por lo que solo eliminaría solo, pero entonces el resultado podría tener el puntero de uno y el recuento de referencia del otro. O casi cualquier combinación de los dos. Sería malo). La afirmación en los documentos de impulso es correcta, ya que obtienes las mismas garantías que obtienes con un incorporado.

RE: EDIT2: la primera situación que está describiendo es muy diferente entre usar built-ins y shared_ptrs. En uno (XCHG y eliminación manual) no hay recuento de referencias; estás asumiendo que eres el único dueño cuando haces esto. Si usa punteros compartidos, está diciendo que otros subprocesos podrían tener propiedad, lo que hace que las cosas sean mucho más complejas. Creo que es posible con una comparación e intercambio, pero esto sería muy poco portátil.

C ++ 0x está saliendo con una biblioteca atómica, lo que debería hacer que sea mucho más fácil escribir código genérico de subprocesos múltiples. Probablemente tendrá que esperar hasta que salga para ver buenas implementaciones de referencia multiplataforma de punteros inteligentes seguros para subprocesos.

No conozco una implementación de puntero tan inteligente, aunque debo preguntar: ¿cómo podría ser útil este comportamiento? Los únicos escenarios en los que podría encontrar actualizaciones de puntero simultáneas son las condiciones de carrera (es decir, errores).

Esto no es una crítica: puede haber un caso de uso legítimo, simplemente no puedo pensar en ello. ¡Avísame!

Re: EDIT2 Gracias por proporcionar un par de escenarios. Parece que las escrituras de puntero atómico serían útiles en esas situaciones. (Una pequeña cosa: para el segundo ejemplo, cuando escribió '' Si no es NULL, evita que el procesador sea destruido al asignarlo a su propio ptr compartido '', espero que haya querido decir que asigna el puntero global compartido a el puntero compartido local primero luego verifique si el puntero compartido local es NULL - la forma en que lo describió es propenso a una condición de carrera donde el puntero compartido global se convierte en NULL después de probarlo y antes de asignarlo al local.)

Puede usar esta implementación Punteros de conteo de referencia atómica para implementar al menos el mecanismo de recuento de referencias.

Su compilador ya puede proporcionar los punteros inteligentes seguros para subprocesos en los estándares más nuevos de C ++. Creo que TBB planea agregar un puntero inteligente, pero no creo que se haya incluido todavía. Sin embargo, es posible que pueda usar uno de los contenedores seguros para subprocesos de TBB.

Puede hacerlo fácilmente incluyendo un objeto mutex con cada puntero compartido y ajustando los comandos de incremento / decremento con el bloqueo.

No creo que esto sea tan fácil, no es suficiente envolver sus clases sh_ptr con un CS. Es cierto que si mantiene una única CS para todos los punteros compartidos, puede asegurarse de evitar el acceso mutuo y la eliminación de objetos sh_ptr entre diferentes subprocesos. Pero esto sería terrible, un objeto CS por cada puntero compartido sería un verdadero cuello de botella. Sería adecuado si cada nuevo ptr -s envolvente tiene CS diferentes 'pero de esta manera deberíamos crear nuestro CS dinámicamente y asegurarnos de que los copiadores de las clases sh_ptr transmitan estos C compartidos. Ahora llegamos al mismo problema: quién pone en cuarentena que este Cs ptr ya está eliminado o no. Podemos ser un poco más inteligentes con las banderas volátiles m_bReleased por instancia, pero de esta manera no podemos atascar las brechas de seguridad entre verificar la bandera y usar las C compartidas. No puedo ver una resolución completamente segura para este problema. Tal vez esas terribles C globales serían el mal menor como matar la aplicación. (perdón por mi inglés)

Esto puede no ser exactamente lo que desea, pero la documentación de boost :: atomic proporciona un ejemplo sobre cómo usar un contador atómico con intrusive_ptr . intrusive_ptr es uno de los punteros inteligentes de Boost, hace `` recuento de referencias intrusivas '', lo que significa que el contador está `` incrustado '' en el destino en lugar de proporcionarlo con el puntero inteligente.

Impulsar atomic Ejemplos de uso:

http://www.boost.org/doc/html/atomic/ use_examples.html

En mi opinión, la solución más fácil es usar un intrusive_ptr con algunas modificaciones menores (pero necesarias).

Compartí mi implementación a continuación:

http://www.philten.com/boost-smartptr-mt/

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top