c++ boost thread-safety shared-ptr

c++ - Implementación shared_ptr completamente segura para subprocesos



boost thread-safety (9)

¿Alguien sabe de una implementación shared_ptr completamente segura para shared_ptr ? Por ejemplo, impulsar la implementación de shared_ptr es seguro para los destinos (refcounting) y también seguro para shared_ptr simultáneas de instancias shared_ptr , pero no para escrituras ni para lectura / escritura.

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

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

Extraño que los doctores dicen que:

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

Pero si compara un puntero ordinario (tipo incorporado) con un 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 sería cuando hay un número de 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. El ptr compartido sería 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 (impidiendo así que se destruya el elemento de trabajo) asignándolo a su propio ptr compartido. Se puede hacer con XCHG y eliminación manual, pero sería bueno si un shared-ptr pudiera hacerlo.

Otro ejemplo es donde el shared-ptr global contiene un "procesador", y es asignado por un hilo y usado por otro hilo. Cuando el subproceso "usuario" ve que el procesador shard-ptr es NULL, usa alguna lógica alternativa para hacer el procesamiento. Si no es NULO, evita que se destruya el procesador asignándolo a su propio ptr compartido.


La escritura simultánea en un puntero incorporado ciertamente no es segura 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 los integradores no son de doble eliminación es porque no se eliminan (y la implementación de boost :: shared_ptr que uso no eliminaría dos veces, ya que usa un incremento y decremento atómico especial, por lo que solo eliminaría un solo, pero el resultado podría tener el puntero de uno y el recuento de ref del otro, o casi cualquier combinación de los dos. Sería malo.). La afirmación en los documentos de actualización es correcta, y obtienes las mismas garantías que con una función incorporada.

RE: EDIT2 - La primera situación que está describiendo es muy diferente entre el uso de built-ins y shared_ptrs. En uno (XCHG y eliminación manual) no hay recuento de referencias; usted está asumiendo que usted es el único propietario cuando hace esto. Si utiliza punteros compartidos, está diciendo que otros subprocesos pueden tener propiedad, lo que hace que las cosas sean mucho más complejas. Creo que es posible con un compare-and-swap, pero esto sería muy no 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 tengas que esperar hasta que salga para ver buenas implementaciones de referencia multiplataforma de punteros inteligentes seguros para subprocesos.


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 aún. Sin embargo, es posible que puedas utilizar uno de los contenedores de subprocesos de TBB.


No sé de una implementación de puntero inteligente, aunque tengo que preguntarme: ¿cómo podría ser útil este comportamiento? Los únicos escenarios en los que puedo pensar donde encontraría actualizaciones de punteros simultáneos 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 eso. ¡Por favor hagamelo saber!

Re: EDIT2 Gracias por proporcionar un par de escenarios. Parece que las escrituras del puntero atómico serían útiles en esas situaciones. (Una pequeña cosa: en el segundo ejemplo, cuando escribiste "Si no es NULL, impide que el procesador se destruya asignándolo a su propio shared-ptr", espero que quisieras decir que asignas el puntero global compartido al puntero compartido local primero y luego compruebe si el puntero compartido local es NULO; la forma en que lo describió es propenso a una condición de carrera en la que el puntero compartido global se convierte en NULO después de probarlo y antes de asignarlo al local).


Agregar las barreras necesarias para una implementación shared_ptr completamente segura para subprocesos probablemente afecte el rendimiento. Considere la siguiente carrera (nota: abunda el pseudocódigo):

Tema 1: global_ptr = A;

Subproceso 2: global_ptr = B;

Tema 3: local_ptr = global_ptr;

Si dividimos esto en sus operaciones constitutivas:

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 hilo 3 lee el puntero después del cambio A, luego B lo borra antes de que se pueda incrementar el recuento de referencias, sucederán cosas malas.

Para manejar esto, necesitamos un valor ficticio para ser utilizado mientras thread 3 está haciendo la actualización refcnt: (nota: compare_exchange (variable, expected, new) reemplaza atómicamente el valor en variable con new si actualmente es igual a new, luego devuelve true 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 átomos en él en el medio de su operación / lectura / operación. Esto no es bueno, puede ser extremadamente costoso en algunas CPU. Además, estás ocupado, esperando también. Puedes empezar a ser inteligente con futexes y otras cosas, pero en ese punto has 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 otorgaría de todos modos, es la razón por la cual generalmente no se ven tales implementaciones shared_ptr seguras para subprocesos. Si necesita algo así, le recomiendo envolver un mutex y shared_ptr en una clase de conveniencia para automatizar el bloqueo.


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


No creo que sea tan fácil, no es suficiente para envolver tus clases sh_ptr con un CS. Es cierto que si mantiene una sola CS para todos los punteros compartidos, puede 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 diferentes CS ''s, pero de esta manera deberíamos crear nuestro CS de forma dinámica, y asegurar que los copiadores de las clases sh_ptr transmitan este C compartido. 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 m_b indicadores lanzados lanzados por instancia, pero de esta manera no podemos pegar las brechas de seguridad entre la verificación de la bandera y el uso de las C compartidas. No puedo ver una resolución completamente segura para este problema. Tal vez esa terrible C global sería la menos grave como matar la aplicación. (Lo siento por mi ingles)



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/


Puede que esto no sea exactamente lo que quieres, pero la documentación 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 objetivo en lugar de proporcionarlo mediante el puntero inteligente.

Impulse atomic ejemplos de uso atomic :

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