2012-04-22 14 views
18

Estoy escribiendo puntero compartido intrusivo y estoy usando C++ 11 <atomic> instalaciones para el contador de referencia. Estos son los fragmentos relevantes de mi código:C++ 11 atomics y cuenta de referencia de puntero compartido intrusivo

//... 
mutable std::atomic<unsigned> count; 
//... 

void 
SharedObject::addReference() const 
{ 
    std::atomic_fetch_add_explicit (&count, 1u, 
     std::memory_order_consume); 
} 

void 
SharedObject::removeReference() const 
{ 
    bool destroy; 

    destroy = std::atomic_fetch_sub_explicit (&count, 1u, 
     std::memory_order_consume) == 1; 

    if (destroy) 
     delete this; 
} 

he empezado con memory_order_acquire y memory_order_release principio, pero luego me convencí de que memory_order_consume debe ser lo suficientemente bueno. Después de una mayor deliberación, me parece que incluso memory_order_relaxed debería funcionar.

Ahora, la pregunta es si puedo usar memory_order_consume para las operaciones o podría usar un orden más débil (memory_order_relaxed) o debería usar un orden más estricto?

+0

Dado que el contador actúa esencialmente como un bloqueo recursivo para la declaración 'delete', diría que" acquire "en' addReference' y "release" en 'removeReference' son los pedidos correctos. ¡Pero su 'addReference' también debería asegurarse de que el contador no fuera cero! –

+0

@KerrekSB: El contador puede ser cero en 'addReference()' después de crear el objeto por primera vez antes de asignarlo a 'SharedPtr <>'. La semántica de adquisición/liberación parece que siempre debería funcionar. ¿Pero no es posible usar una restricción de ordenamiento más débil y por qué no? – wilx

+0

Acerca del cero: supongamos que el recuento es 1. Ahora el subproceso 1 quiere eliminar el objeto y resta las llamadas. Si en este punto el subproceso 2 desea * aumentar * el número de subprocesos, aumenta de cero a uno, pero el subproceso 1 continuará y eliminará el objeto de todos modos. Eso debe ser evitado. –

Respuesta

20
void 
SharedObject::addReference() const 
{ 
    std::atomic_fetch_add_explicit (&count, 1u, std::memory_order_relaxed); 
} 

void 
SharedObject::removeReference() const 
{ 
    if (std::atomic_fetch_sub_explicit (&count, 1u, std::memory_order_release) == 1) { 
     std::atomic_thread_fence(boost::memory_order_acquire); 
     delete this; 
    } 
} 

que desea utilizar atomic_thread_fence tal que el delete es estrictamente después de la fetch_sub. Reference

Cita del texto vinculado:

El aumento del contador de referencia siempre se puede hacer con memory_order_relaxed: New referencias a un objeto sólo se pueden formar de una referencia existente, y que pasa una referencia existente desde un hilo a otro ya debe proporcionar cualquier sincronización requerida.

Es importante hacer cumplir cualquier posible acceso al objeto en un hilo (a través de una referencia existente) antes de eliminar el objeto en un hilo diferente. Esto se logra mediante una operación de "liberación" después de eliminar una referencia (cualquier acceso al objeto a través de esta referencia debe obviamente ocurrir antes), y una operación "adquirir" antes de eliminar el objeto.

Sería posible utilizar memory_order_acq_rel para la operación fetch_sub , pero esto se traduce en operaciones que no sean necesarios "adquirir" cuando el contador referencia todavía no llega a cero y puede imponer una penalización de rendimiento .

+0

Si bien ya acepté la respuesta e implementé mi puntero intrusivo de esa manera, la cita me hizo pensar un poco y aquí hay otra pregunta. ¿Qué tal relajar el decremento a 'memory_order_relaxed' y hacer que la verdadera rama del' if() 'use' memory_order_acq_rel'? – wilx

+0

con su '_relaxed', luego' _acq_rel', no hay un orden estricto de 'fetch_sub (_relaxed)' y 'fence (_acq_rel)' then 'delete'. Usted puede estar interesado en esta [página] (http://www.chaoticmind.net/~hcb/projects/boost.atomic/doc/atomic/thread_coordination.html). – user2k5

+0

Ok, después de leer la página creo que entiendo que no puede ser 'fetch_sub (_relaxed)'. Es porque cualquier operación '_relaxed' no está ordenada contra otras operaciones en absoluto. ¿Pero qué pasa con 'fetch_sub (_consume)' y 'fence (_acq_rel)'? Ahí me parece que 'fetch_sub (_consume)' plantea una barrera de reordenación del compilador y 'fence (_acq_rel)' ordena todas las cargas y almacena contra el resto de la máquina y el siguiente 'delete' tendrá una vista consistente de la memoria. ¿O estoy equivocado? – wilx

Cuestiones relacionadas