2008-09-18 6 views
19

¿Cómo implementar un sistema de conteo de referencias eficiente y seguro de subprocesos en CPUs X86 en el lenguaje de programación C++?Cómo implementar el conteo de referencias seguro de subprocesos en C++

Siempre me encuentro con el problema de que las operaciones críticas no atómicas, y las operaciones de enclavamiento X86 disponibles no son suficientes para implementar el sistema de recuento de ref.

El siguiente artículo trata este tema, pero requiere instrucciones especiales de CPU:

http://www.ddj.com/architect/184401888

Respuesta

11

Hoy en día, se puede utilizar el Boost/TR1 shared_ptr <> puntero inteligente para mantener las referencias contadas su referencia.

Funciona muy bien; sin alboroto, sin muss. La clase shared_ptr <> se encarga de todo el bloqueo necesario en el refcount.

+0

también tenga en cuenta que el Informe técnico 1 (TR1) es un borrador de artículos para su inclusión en el estándar C++ 0x. A medida que C++ 0x se acerca, estos también serán mejor respaldados (se basan en la biblioteca de Boost). –

+0

Tengo mis dudas sobre la implementación sin bloqueo, pero esta es probablemente la mejor opción. Multi-Thread es terriblemente difícil de hacer en C++ con todos los trucos que el compilador puede hacerle a su código. –

+9

Hombre ... O me estoy perdiendo algo, o algo está mal con esta respuesta ... ¿Por qué fue aceptado y votado tantas veces, si ni siquiera responde la pregunta original? Simplemente sugiere algo ¿más que ya tiene una implementación funcional de lo mismo que estaba en OQ? – Paulius

3

Estrictamente hablando, tendrá que esperar hasta C++ 0x para poder escribir código seguro para subprocesos en C++ puro.

Por ahora, puede usar Posix, o crear sus propias envolturas independientes de plataforma en comparación e intercambiar y/o aumentar/disminuir.

+1

O haga como todos los demás y use Boost :: TR1 – gnud

+0

Si no le importa Win32, Posix es la mejor opción (rápida y sencilla), IMO. Por supuesto, actualmente los compiladores principales admiten C++ 0x, lo que significa que puede usar TR1 (que es parte del estándar C++ 11). Solo tiene que activar la opción (indicador del compilador C++ 0x). – Pijusn

4

En VC++, puede usar _InterlockedCompareExchange.

do 
    read the count 
    perform mathematical operation 
    interlockedcompareexchange(destination, updated count, old count) 
until the interlockedcompareexchange returns the success code. 

en otras plataformas/compiladores, utilice la intrínseca apropiados para la instrucción LOCK cmpxchg que _InterlockedCompareExchange de MS expone.

+1

O simplemente enclaustramientoIncremento/Decremento. – yrp

+0

@yrp: ¿es más rápido que un ciclo while/comparar y cambiar? AFAIK, IntelockedIncrement/Decrement emiten instrucciones LOCK INC/LOCK DEC x86, mientras que cas no (al menos LOCK está implícito para esta instrucción). – Stringer

0

Si la instrucción en sí misma no es atómica, entonces necesita hacer que la sección de código que actualiza la variable apropiada sea una sección crítica.

es decir, Debe evitar que otros hilos entren en esa sección de código utilizando algún esquema de bloqueo. Por supuesto, los bloqueos deben ser atómicos, pero puede encontrar un mecanismo de bloqueo atómico dentro de la clase pthread_mutex.

La cuestión de eficiente: La biblioteca pthread es tan eficiente como puede y aún así garantiza que el bloqueo mutex es atómico para su sistema operativo.

Es caro: Probablemente. Pero para todo lo que requiere una garantía, hay un costo.

0

Ese código particular publicado en ese artículo ddj agrega complejidad adicional para dar cuenta de errores en el uso de punteros inteligentes.

Específicamente, si no puede garantizar que el puntero inteligente no cambiará en una asignación a otro puntero inteligente, lo está haciendo mal o está haciendo algo muy poco confiable para empezar. Si el puntero inteligente puede cambiar mientras está siendo asignado a otro puntero inteligente, eso significa que el código que realiza la asignación no posee el puntero inteligente, que es sospechoso para empezar.

2

Win32 InterlockedIncrementAcquire and InterlockedDecrementRelease (si quiere estar seguro y le importan las plataformas con posible reordenamiento, por lo tanto necesita emitir barreras de memoria al mismo tiempo) o InterlockedIncrement y InterlockedDecrement (si está seguro de que permanecerá x86), son atómicos y harán el trabajo.

Dicho esto, Boost/TR1 shared_ptr <> manejará todo esto por ti, por lo tanto, a menos que necesites implementarlo por tu cuenta, probablemente harás lo mejor para cumplirlo.

1

Tenga en cuenta que el bloqueo es muy costoso y ocurre cada vez que entrega objetos entre punteros inteligentes, incluso cuando el objeto pertenece actualmente a un hilo (la biblioteca del puntero inteligente no lo sabe).

Teniendo en cuenta esto, puede haber una regla general aplicable en este caso (estoy feliz de ser corregido!)

Si las cosas del siguiente aplique en su caso:

  • Usted tiene estructuras de datos complejas que sería difícil escribir destructores para (o donde la semántica del valor de estilo STL sería inapropiada, por diseño) por lo que necesita punteros inteligentes para hacerlo por usted, y
  • Está usando múltiples hilos que comparten estos objetos, y
  • Le importa por el rendimiento y la corrección

... entonces la recolección de basura real puede ser una mejor opción. A pesar de que GC tiene una mala reputación por el rendimiento, todo es relativo. Creo que se compara muy favorablemente con el bloqueo de punteros inteligentes. Fue una parte importante de por qué el equipo de CLR eligió el verdadero GC en lugar de algo usando el recuento de referencias. Ver this article, en particular, esta cruda comparación de lo que significa la asignación de referencia si ha contando pasando:

sin ref-conteo:

a = b; 

ref conteo:

if (a != null) 
    if (InterlockedDecrement(ref a.m_ref) == 0) 
      a.FinalRelease(); 

if (b != null) 
    InterlockedIncrement(ref b.m_ref); 

a = b; 
+0

¿Qué sucede cuando otro thread se intercala entre 'InterlockedDecrement (...)' y 'a.FinalRelease();'? – smoku

+0

Es imposible en el diseño; este código se generó, no fue escrito a mano. El código de la aplicación solo dice 'a = b'. Si dos hilos tienen acceso al objeto suficiente para incrementar el conteo, entonces el recuento ya es> = 2. Cualquiera de los dos puede disminuirlo, pero al hacerlo, renuncia simultáneamente a la capacidad de incrementarlo. –

+0

¿Qué sucede cuando otro hilo hace 'a = b' entre 'InterlockedDecrement (...)' y 'a.FinalRelease();'? – smoku

Cuestiones relacionadas