2012-01-25 8 views
7

Czy jest to poprawna implementacja ogólnej funkcji zamiany atomów? Szukam rozwiązania zgodnego z C++ 03 na GCC.Funkcja zamiany atomów przy użyciu wbudowanych atomowych gcc

template<typename T> 
void atomic_swap(T & a, T & b) { 
    static_assert(sizeof(T) <= sizeof(void*), "Maximum size type exceeded."); 
    T * ptr = &a; 
    b =__sync_lock_test_and_set(ptr, b); 
    __sync_lock_release(&ptr); 
} 

Jeśli nie, co należy zrobić, aby to naprawić?

Również: czy __sync_lock_release jest zawsze potrzebny? Podczas przeszukiwania innych baz kodów okazało się, że często nie jest to wywoływane. Bez uwolnienia nazwać mój kod wygląda następująco:

template<typename T> 
void atomic_swap(T & a, T & b) { 
    static_assert(sizeof(T) <= sizeof(void*), "Maximum size type exceeded."); 
    b = __sync_lock_test_and_set(&a, b); 
} 

PS: Atomic swap in GNU C++ jest podobne pytanie, ale to nie jest odpowiedź na moje pytanie, ponieważ podana odpowiedź wymaga C++ 11 na std::atomic i ma podpis Data *swap_data(Data *new_data) który nie robi” w przypadku funkcji swap wydaje się mieć sens. (Faktycznie zamienia podany argument na globalną zmienną, która została zdefiniowana przed funkcją.)

+0

Wygląda na to, że tylko dostęp do 'a' ma być atomowy? –

+0

Po co wynajdować koło? Zobacz http://concurrencykit.org/ –

+0

@BenVoigt Ten post nie daje jednoznacznej odpowiedzi. I zamienia argument z globalną zmienną. – StackedCrooked

Odpowiedz

9

Należy pamiętać, że ta wersja wymiany nie jest w pełni operacją atomową. Podczas gdy wartość b zostanie atomicznie skopiowana do a, wartość a może skopiować inną modyfikację o wartości b przez inny wątek. Innymi słowy, przypisanie do b nie jest atomowe w odniesieniu do innych nici. W ten sposób może dojść do sytuacji, w której a == 1 i b == 2, a po wbudowaniu gcc, otrzymasz w końcu a == 2 i zwracaną wartość 1, ale teraz inny wątek zmienił wartość b na 3, a Ty przepisz tę wartość w b o wartości 1. Więc podczas gdy "technicznie" zamieniłeś wartości, nie zrobiłeś tego atomowo ... inny wątek dotknął wartości b pomiędzy zwrotem z wbudowanego atomu gcc, a przypisaniem tej zwracanej wartości do b. Spojrzeć z zespół stand-punkt, trzeba coś jak następuje:

lea RAX, qword ptr [RDI] // T * ptr = &a; 
mov RCX, qword ptr [RSI] // copy out the value referenced by b into a register 
xchg [RAX], RCX   // __sync_lock_test_and_set(&a, b) 
mov qword ptr [RSI], RCX // place the exchange value back into b (not atomic!!) 

Szczerze mówiąc, nie można zrobić blokady wolne atomową zamianę dwóch oddzielnych miejscach pamięci bez obsługi sprzętowej jak DCAS lub słabo związany z obciążeniem/przechowywanie warunkowe, lub ewentualnie z wykorzystaniem innej metody, takiej jak pamięć transakcyjna (która sama w sobie ma tendencję do stosowania drobnoziarnistego blokowania).

Po drugie, jeśli twoja funkcja jest teraz napisana, jeśli chcesz, aby twoja operacja atomowa miała zarówno semantykę, jak i semantykę, to tak, będziesz musiał albo umieścić w __sync_lock_release, albo zamierzasz trzeba dodać pełną barierę pamięci przez __sync_synchronize. W przeciwnym razie uzyska tylko semantykę na __sync_lock_test_and_set. Nadal jednak nie zamienia atomowo dwóch osobnych lokalizacji pamięci ...

+0

Rozumiem, że zapis do 'b' czyni operację nieatomową, a zatem nie jest bezpieczna dla wątków. Jednak nie widzę sposobu, aby to uczynić bezpiecznym. Czy to oznacza, że ​​wymiana typów wskaźników bez blokady nie jest możliwa? – StackedCrooked

+0

Dość dużo, chyba że ponownie sprzęt obsługuje coś takiego jak DCAS lub operację warunkową load-linked/store, która jest dość słaba, a więc kończy się niepowodzeniem, jeśli istnieje jakikolwiek dostęp do pamięci lub przynajmniej dostęp do pamięci na linii pamięci podręcznej , a dwie wartości, które wymieniasz, znajdują się na tej samej linii pamięci podręcznej – Jason

Powiązane problemy