2012-04-22 12 views
18

Piszę uciążliwy udostępniony wskaźnik i używam obiektów C++ 11 <atomic> dla licznika odwołań. Oto stosowne fragmenty mojego kodu:C++ 11 atomów i natarczywa dzielona liczba odnośników wskaźnik

//... 
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; 
} 

zacząłem z memory_order_acquire i memory_order_release ale potem przekonałem się, że memory_order_consume powinny być wystarczająco dobre. Po dalszych rozważaniach wydaje mi się, że powinien zadziałać nawet memory_order_relaxed.

Teraz pytanie brzmi, czy mogę użyć memory_order_consume do operacji lub czy mogę użyć słabszego zamawiania (memory_order_relaxed), czy też powinienem zastosować bardziej rygorystyczne zamówienie?

+0

Ponieważ licznik działa zasadniczo jako blokada rekursywna dla instrukcji 'delete', powiedziałbym, że" nabywanie "w' addReference' i "release" w 'removeReference' są poprawnymi porządkami. Ale twój 'addReference' powinien także upewnić się, że licznik nie jest równy zeru! –

+0

@KerrekSB: Licznik może mieć wartość zero w 'addReference()' po utworzeniu obiektu przed przypisaniem go do 'SharedPtr <>. Selekcja/semantyka wydaje się, że powinna zawsze działać. Ale czy nie można użyć słabszego ograniczenia zleceń, a dlaczego nie? – wilx

+0

O zera: załóżmy, że refcount wynosi 1. Teraz wątek 1 chce usunąć obiekt i wywołuje odejmowanie. Jeśli w tym momencie wątek 2 chce * zwiększyć * liczbę wątków, zwiększa się od zera do jednego, ale wątek 1 będzie kontynuowany i mimo to usunie obiekt. Tego należy unikać. –

Odpowiedz

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; 
    } 
} 

Chcesz użyć atomic_thread_fence takie, że delete jest ściśle po fetch_sub. Reference

Cytat połączonego tekstu:

Zwiększenie licznika odniesienia zawsze można zrobić z memory_order_relaxed: Nowe odniesienia do obiektu mogą być tworzone tylko z istniejącego odniesienia i przechodzącej istniejący odwołanie od jeden wątek musi już zapewniać wymaganą synchronizację.

Ważne jest, aby wymusić możliwy dostęp do obiektu w jednym wątku (przez istniejącą referencję) przed usunięciem obiektu w innym wątku. Osiąga się to przez operację "zwolnienie" po zrzuceniu referencji (dowolny dostęp do obiektu poprzez to odniesienie musi oczywiście nastąpić wcześniej) i operację "pozyskiwania" przed usunięciem obiektu.

byłoby możliwe użycie memory_order_acq_rel dla funkcjonowania fetch_sub , ale powoduje to niepotrzebne „zdobyć” operacji, gdy licznik odniesienie jeszcze nie dotrzeć do zera i może nałożyć karę wydajności .

+0

Chociaż już zaakceptowałem odpowiedź i zaimplementowałem w ten sposób moją natrętną wskazówkę, cytat nieco mnie zastanowił, a tu jest inne pytanie. Co powiesz na rozładowanie dekrementacji na 'memory_order_relaxed' i utworzenie prawdziwej gałęzi' if() 'używając' memory_order_acq_rel'? – wilx

+0

z '_relaxed', a następnie' _acq_rel', nie ma ścisłego porządkowania 'fetch_sub (_relaxed)' i 'fence (_acq_rel)', a następnie 'delete'. Możesz być zainteresowany tą [strona] (http://www.chaoticmind.net/~hcb/projects/boost.atomic/doc/atomic/thread_coordination.html). – user2k5

+0

OK, po przeczytaniu strony Myślę, że rozumiem, że nie może to być 'fetch_sub (_relaxed)'. Dzieje się tak, ponieważ żadna operacja "_relaxed" nie jest w ogóle zamawiana na inne operacje. Ale co z 'fetch_sub (_consume)' i 'fence (_acq_rel)'? Wydaje mi się, że 'fetch_sub (_consume)' tworzy barierę zmiany kolejności kompilatora i 'fence (_acq_rel)' porządkuje wszystkie ładunki i przechowuje się względem reszty maszyny i następujące 'delete' będzie miało spójny widok pamięci. Czy ja się mylę? – wilx