2009-06-13 12 views
10

Piszę program z wątkiem konsumenckim i wątkiem producenta, teraz wydaje się, że synchronizacja kolejki jest dużym obciążeniem w programie i szukałem niektórych implementacji kolejki wolnych od blokady, ale znalazłem tylko wersję Lamport i ulepszoną wersję na PPoPP '08:Dowolna implementacja kolejki wolnej od blokady pojedynczego klienta w C?

enqueue_nonblock(data) { 
    if (NULL != buffer[head]) { 
     return EWOULDBLOCK; 
    } 
    buffer[head] = data; 
    head = NEXT(head); 
    return 0; 
} 

dequeue_nonblock(data) { 
    data = buffer[tail]; 
    if (NULL == data) { 
     return EWOULDBLOCK; 
    } 
    buffer[tail] = NULL; 
    tail = NEXT(tail); 
    return 0; 
} 

Obie wersje wymagają wstępnie przydzielona tablicę dla danych, moje pytanie jest to, że jest jakiś pojedynczy pojedynczy konsument-producent-lock-wolna implementacja kolejki, która używa malloc(), aby przydzielić miejsca dynamicznie ?

Kolejnym pokrewnym pytaniem jest: w jaki sposób mogę zmierzyć dokładny narzut w synchronizacji kolejki? Na przykład ile czasu zajmuje pthread_mutex_lock(), itp.

Odpowiedz

6

Jeśli martwisz się o wydajność, dodanie funkcji malloc() do miksu nie pomoże. A jeśli nie martwisz się wydajnością, możesz po prostu kontrolować dostęp do kolejki za pomocą muteksu. Czy rzeczywiście zmierzyłeś wydajność takiej implementacji? Wydaje mi się, że idziesz w dół na rodzinną ścieżkę przedwczesnej optymalizacji.

+0

Zgadzam się z tobą punkt malloc ale nie mutex. Zablokuj zabójstwa. Tak więc jeden producent i jeden konsument blokują darmowe oprogramowanie i należy z tego korzystać. Teraz ten konsument może zastosować logikę shardingu do przekazywania danych różnym konsumentom. LOCK zabija. – siddhusingh

4

Algorytm, który pokazujesz, działa, ponieważ chociaż dwa wątki współużytkują zasób (tj. Kolejkę), udostępniają go w bardzo szczególny sposób. Ponieważ tylko jeden wątek zmienia indeks nagłówka kolejki (producenta) i tylko jeden wątek zmienia indeks końcowy (oczywiście konsumenta), nie można uzyskać niespójnego stanu współdzielonego obiektu. Ważne jest również, aby producent umieścił aktualne dane w numerze , zanim zaktualizuje indeks głowicy, a konsument odczyta dane, które chce , przed aktualizacją indeksu ogona przed.

Działa tak dobrze jak ma b/c tablica jest dość statyczna; oba wątki mogą liczyć na pamięć dla znajdujących się tam elementów. Prawdopodobnie nie możesz całkowicie zastąpić tablicy, ale możesz zmienić to, do czego służy tablica.

Np. Zamiast przechowywać dane w tablicy, użyj go, aby zachować wskaźniki dla danych. Następnie możesz malloc() i free() elementy danych, przekazując referencje (wskaźniki) między wątkami za pośrednictwem tablicy.

Ponadto posix obsługuje odczyt nanosekundowego zegara, chociaż rzeczywista precyzja zależy od systemu. Możesz odczytać ten zegar wysokiej rozdzielczości przed i po i po prostu odjąć.

+4

Z pewnością ten algorytm wymaga dodania niektórych barier pamięci? – bdonlan

+1

Tak .. Mówi, że "Ważne jest również, aby producent umieścił aktualne dane przed aktualizacją indeksu głównego, a konsument odczytał dane, które chce, przed aktualizacją indeksu ogumienia." " – ben

+1

@bdonlan: (et al) nie tak. jest całkowicie uzależniony od kolejności operacji i faktu, że jest to pojedynczy producent, pojedynczy konsument. w tych okolicznościach jest w porządku. – JustJeff

2

Przypominam sobie, że widziałem coś, co wyglądało interesująco kilka lat temu, choć nie mogę tego teraz znaleźć. :(Zaproponowana implementacja bez blokady wymagała użycia CAS primitive, chociaż nawet implementacja blokowania (jeśli nie chciałeś używać prymitywu CAS) miała całkiem dobre cechy charakterystyczne - blokady blokowały tylko wielu czytelników lub wielu producentów nie uderzyło w kolejkę w tym samym czasie, producent wciąż nie ścigał się z klientem.)

Pamiętam, że podstawową koncepcją stojącą za kolejką było stworzenie połączonej listy, która zawsze miała jeden dodatkowy "pusty" węzeł w Ten dodatkowy węzeł oznaczał, że nagłówek i nagłówek listy odnosiłyby się tylko do tych samych danych, kiedy lista była pusta.Pragnęłbym znaleźć papier, nie robię algorytmu sprawiedliwości z moim wyjaśnieniem. ..

AH-ha!

Znalazłem kogoś, kto przepisał the algorithm without the remainder of the article. To może być przydatny punkt wyjścia.

+0

I najlepiej przeczytać drobnym drukiem w tym adresie URL (poszukaj "powerpc") i miej to na uwadze, gdy zaczniesz wymyślać własne konstrukcje bez blokady. –

+0

Podany opis dotyczy pracy Michaela i Scotta - a z powyższego komentarza widzę, że rzeczywiście jest to dzieło; psuedokod jest pobierany bezpośrednio z papieru. Idea węzła typu dummy pochodzi od Valois. –

2

Pracowałem z dość prostą implementacją kolejki, która spełnia większość twoich kryteriów. Użył on statycznej puli o maksymalnej wielkości bajtów, a następnie zaimplementowaliśmy w niej komunikaty. Był wskaźnik, który przesuwałby się jeden proces, oraz wskaźnik końcowy, który przesuwałby inny proces.

Zamki były nadal wymagane, ale użyliśmy Peterson's 2-Processor Algorithm, który jest dość lekki, ponieważ nie obejmuje wywołań systemowych. Blokada jest wymagana tylko w bardzo małym, dobrze ograniczonym obszarze: maksymalnie kilka cykli procesora, więc nigdy nie blokujesz na długo.

1

Myślę, że alokator może być problem z wydajnością. Możesz spróbować użyć niestandardowego wielowątkowego alokatora pamięci, który używa listy połączonej do utrzymywania zwolnionych bloków. Jeśli twoje bloki nie są (prawie) tego samego rozmiaru, możesz zaimplementować "Przydzielanie pamięci systemowi Buddy", co jest bardzo szybkie. Musisz zsynchronizować swoją kolejkę (bufor pierścieniowy) z muteksem.

Aby uniknąć zbyt dużej synchronizacji, możesz spróbować zapisać/odczytać wiele wartości do/z kolejki przy każdym dostępie.

Jeśli nadal chcesz używać algorytmów wolnych od blokady, musisz użyć wcześniej przydzielonych danych lub użyć przydziału wolnego od blokady. Jest papier o lock-wolny podzielnika „Scalable lock-nieodpłatny przydział pamięci dynamicznej” i implementacja Streamflow

Przed rozpoczęciem blokady darmowe rzeczy, spojrzeć na: Circular lock-free buffer

3

Tak.

Istnieje wiele pozbawionych blokad wielokrotnych pisarzy wielokrotnego zapisu.

Wdrożyłem jedną, przez Michaela i Scotta, z ich artykułu z 1996 roku.

Będę (po kilku innych testach) wypuścić małą bibliotekę pozbawionych blokad struktur danych (w C), która będzie zawierać tę kolejkę.

+0

1. Te używają węzłów malloc, które mają tendencję do zabijania wydajności. 2. Ten algorytm używa CAS - CAS umieszcza blokadę w pamięci i dlatego jest gorszy od powyższego. W rzeczywistości w przypadkach, gdy zamki rzadko są trzymane (np. Szybkie zamki), CAS == SpinLock na wielu rdzeniach. Chciałbym go jednak obejrzeć. – ben

+0

OP prosi o malloc. Biblioteka jest tutaj; http://www.liblfds.org –

1

Dodanie obiektu malloc może zabić wzrost wydajności, jaki możesz osiągnąć, a struktura blokująca byłaby równie skuteczna. Dzieje się tak dlatego, że malloc wymaga pewnego rodzaju blokady CAS nad stertą, a zatem niektóre formy malloc mają swoją własną blokadę, więc możesz zablokować menadżera pamięci.

Aby użyć malloc będzie trzeba wstępnie przeznaczyć wszystkie węzły i zarządzać nimi z innej kolejki ...

Uwaga Można zrobić jakąś formę rozszerzalna tablicy, która musiałaby zablokować jeśli został rozszerzony.

Ponadto, gdy są zablokowane, są zablokowane na procesorze, powodują blokadę pamięci i blokują pamięć przez czas trwania instrukcji i często blokują potok.

3

Powinieneś spojrzeć na bibliotekę FastFlow

Powiązane problemy