2016-03-22 20 views
8

W trakcie próby zrozumienia, jak radzić sobie z kodem bez blokady, próbowałem napisać pojedynczą blokadę wolnego klienta/pojedynczego producenta. Jak zwykle sprawdzałem artykuły, artykuły i kod, szczególnie biorąc pod uwagę, że jest to dość delikatny temat.Używanie std :: memory_order_consume w kolejce SPV w kolejce wolnych SPL

Tak, natknąłem się na wdrożenie tej struktury danych w bibliotece Folly, które można znaleźć tutaj: https://github.com/facebook/folly/blob/master/folly/ProducerConsumerQueue.h

As każdego zamka wolne kolejce widziałem, ten wydaje się używać buforze cyklicznym, więc mamy dwie zmienne std::atomic<unsigned int>: readIndex_ i writeIndex_. readIndex_ wskazuje następny indeks, przy którym będziemy czytać, i writeIndex_ następny, przy którym będziemy pisać. Wydaje się dość proste.

Tak więc wdrożenie wydaje się być czyste i dość proste od pierwszego wejrzenia, ale znalazłem jedną rzecz kłopotliwą. Rzeczywiście, niektóre funkcje, takie jak isEmpty(), isFull() lub guessSize(), używają std::memory_order_consume do pobierania wartości indeksów.

I szczerze mówiąc, naprawdę nie wiem, w jakim celu służą. Nie zrozumcie mnie źle, zdaję sobie sprawę z zastosowania std::memory_order_consume w klasycznym przypadku przenoszenia zależności przez wskaźnik atomowy, ale tutaj wydaje się, że nie mamy żadnej zależności! Właśnie dostaliśmy indeksy, liczby całkowite bez znaku, nie tworzymy zależności. Dla mnie w tym scenariuszu std::memory_order_relaxed jest równoważny.

Jednak nie ufam sobie, że lepiej rozumiem pamięć zamawianą niż ci, którzy opracowali ten kod, dlatego dlatego zadaję to pytanie tutaj. Czy jest coś, co przegapiłem lub źle zrozumiałem?

Z góry dziękuję za odpowiedzi!

+1

Niewiele jest przypadków, w których 'std :: memory_order_relaxed' faktycznie będzie miało jakąkolwiek różnicę.Naprawdę potrzebujesz mikroukładu zdolnego do rozdarcia odczytu/zapisu. Przykładem jest x86, gdy mamy do czynienia z danymi niewyrównanymi, ale ty nie. Powodem, dla którego mówię to wszystko jest to, że jeśli myślisz, że coś wymaga 'memory_order_relaxed', prawdopodobnie nie rozumiesz użycia. Czy próbujesz zapobiec rozdarciu odczytu/zapisu? – SergeyA

+1

@SideEffects: Zgadzam się z tobą. Nie ma późniejszych dostępów do pamięci, które zależą od wartości indeksu w tych funkcjach, więc nie rozumiem, w jaki sposób 'std :: memory_order_consume' mógłby wnieść coś użytecznego. Autor (s) Adresy e-mail są na górze pliku; może spróbuj wysłać je e-mailem? (Jeśli to zrobisz, dodaj tutaj aktualizację lub odpowiedź.) Ciekaw jestem, czy czegoś brakuje.) – Nemo

+0

@SergeyA Tak, być może było to trochę niejasne. Chciałem tylko powiedzieć, że uważałem, że porządkowanie pamięci konsumpcyjnej było, przynajmniej dla mnie, nie dodawaniem niczego użytecznego. Ale ja, może chcę wyjaśnić ten punkt, dzięki! – SideEffects

Odpowiedz

3

myślałem to samo kilka miesięcy temu, więc złożyłam this pull request w październiku, co sugeruje, że zmiany std::memory_order_consume ładunki do std::memory_order_relaxed ponieważ zużywają po prostu nie ma sensu, ponieważ nie było żadnych zależności, które mogą być przenoszone z jednego wątku do innego za pomocą tych funkcji. Skończyło się na generowaniu jakąś dyskusję, która ujawniła, że ​​ewentualny przypadek użycia dla isEmpty(), isFull() i sizeGuess był następujący:

//Consumer  
while(queue.isEmpty()) {} // spin until producer writes 
use_queue(); // At this point, the writes from producer _should_ be visible 

dlatego wyjaśnili, że std::memory_order_relaxed nie byłoby właściwe i std::memory_order_consume będzie. Jest to jednak prawdą tylko dlatego, że std::memory_order_consume jest promowane na std::memory_order_acquire we wszystkich znanych kompilatorach. Tak więc chociaż wydaje się, że std::memory_order_consume zapewnia właściwą synchronizację, jest to dość mylące, aby pozostawić to w kodzie i założyć, że pozostanie ono poprawne, zwłaszcza jeśli std::memory_order_consume zostanie kiedykolwiek zaimplementowane zgodnie z przeznaczeniem. Powyższy przypadek użycia nie zadziałałby na słabszych architekturach, ponieważ nie zapewniłaby odpowiedniej synchronizacji.

To, czego naprawdę potrzebują, to wykonanie tych obciążeń std::memory_order_acquire, aby działały zgodnie z zamierzeniami, dlatego przesłałem this other pull request kilka dni temu. Alternatywnie, mogą podjąć nabyć-ładunki z pętli i używać ogrodzenie na końcu:

//Consumer  
while(queue.isEmpty()) {} // spin until producer writes using relaxed loads 
std::atomic_thread_fence(std::memory_order_acquire); 
use_queue(); // At this point, the writes from producer _should_ be visible 

Tak czy inaczej, std::memory_order_consume jest niewłaściwie stosowany tutaj.

+0

Dzięki za poświęcenie czasu na odpowiedź! Przypadkowo miałem właśnie wysłać im e-mail, tuż przed odpowiedzią. Jest więc bardziej sensowny, nawet jeśli implementacja nie jest zgodna z definicją standardu. Wciąż całkiem dziwna gwarancja dla metody "isEmpty()" dla mnie. – SideEffects

+0

Zgadzam się, semantyka "konsumuj" na tego rodzaju funkcji wydaje się nie na miejscu. Zwiększenie nawet ma swoje równoważne funkcje za pomocą 'zrelaksowanego'. Cieszę się, że ktoś inny też to znalazł, wiedziałem, że nie mogę być jedyny! – Alejandro

Powiązane problemy