Czy to prawda, że C ++ 0x przyjdzie bez semaforów? Istnieje już kilka pytań dotyczących przepełnienia stosu dotyczących używania semaforów. Używam ich (semaforów posix) cały czas, aby pozwolić wątkowi czekać na jakieś zdarzenie w innym wątku:
void thread0(...)
{
doSomething0();
event1.wait();
...
}
void thread1(...)
{
doSomething1();
event1.post();
...
}
Gdybym zrobił to z muteksem:
void thread0(...)
{
doSomething0();
event1.lock(); event1.unlock();
...
}
void thread1(...)
{
event1.lock();
doSomethingth1();
event1.unlock();
...
}
Problem: Jest brzydki i nie ma gwarancji, że Thread1 najpierw zablokuje muteks (biorąc pod uwagę, że ten sam wątek powinien blokować i odblokowywać muteks, nie możesz również zablokować zdarzenia1 przed uruchomieniem wątku0 i wątku1).
Skoro więc doładowanie również nie ma semaforów, jaki jest najprostszy sposób osiągnięcia powyższego?
Odpowiedzi:
Możesz łatwo zbudować go z muteksu i zmiennej warunku:
źródło
while(!count_)
.Bazując na odpowiedzi Maxima Yegorushkina , spróbowałem zrobić przykład w stylu C ++ 11.
źródło
cv.wait(lck, [this]() { return count > 0; });
Zdecydowałem się napisać najsolidniejszy / generyczny semafor C ++ 11, jaki mogłem, w stylu standardowym, jak tylko potrafię (uwaga
using semaphore = ...
, normalnie używałbyś nazwysemaphore
podobnej do normalnie używanejstring
niebasic_string
):źródło
wait_for
iwait_until
wywołania metody z orzecznika zwrócić wartość logiczną (a nie `std :: cv_status).std::size_t
jest bez znaku, więc zmniejszenie go poniżej zera to UB i zawsze będzie>= 0
. IMHOcount
powinno byćint
.zgodnie z semaforami posix, dodałbym
I zdecydowanie wolę używać mechanizmu synchronizacji na dogodnym poziomie abstrakcji, zamiast zawsze kopiować wklejanie zszytej wersji przy użyciu bardziej podstawowych operatorów.
źródło
Możesz również sprawdzić cpp11-on-multicore - ma przenośną i optymalną implementację semafora.
Repozytorium zawiera również inne dodatki do wątków, które uzupełniają wątki w języku c ++ 11.
źródło
Możesz pracować ze zmiennymi mutex i warunkowymi. Uzyskujesz wyłączny dostęp z muteksem, sprawdź, czy chcesz kontynuować, czy musisz czekać na drugi koniec. Jeśli musisz czekać, czekasz w stanie. Gdy inny wątek stwierdzi, że możesz kontynuować, sygnalizuje stan.
W bibliotece boost :: thread jest krótki przykład , który najprawdopodobniej możesz po prostu skopiować (biblioteki wątków C ++ 0x i boost są bardzo podobne).
źródło
wait()
przetłumacz ją na "blokuj, sprawdź licznik, jeśli nie zerową dekrementację i kontynuuj; jeśli zero czeka na warunek" whilepost
będzie "blokada, licznik przyrostów, sygnał, jeśli było 0 "Może być również przydatna otoka semafora RAII w wątkach:
Przykład użycia w aplikacji wielowątkowej:
źródło
C ++ 20 wreszcie będzie miał semafory -
std::counting_semaphore<max_count>
.Będą one miały (przynajmniej) następujące metody:
acquire()
(bloking)try_acquire()
(bez blokowania, zwraca natychmiast)try_acquire_for()
(bez blokowania, trwa pewien czas)try_acquire_until()
(bez blokowania, zajmuje trochę czasu, aby przestać próbować)release()
Tego jeszcze nie ma na liście cppreference, ale możesz przeczytać te slajdy prezentacji CppCon 2019 lub obejrzeć wideo . Jest też oficjalna propozycja P0514R4 , ale nie jestem pewien, czy jest to najbardziej aktualna wersja.
źródło
Okazało się, że shared_ptr i słabe_ptr, długie z listą, wykonały pracę, której potrzebowałem. Mój problem polegał na tym, że kilku klientów chciało wchodzić w interakcje z wewnętrznymi danymi hosta. Zazwyczaj host aktualizuje dane samodzielnie, jednak jeśli klient tego zażąda, musi przerwać aktualizację, dopóki żaden klient nie uzyska dostępu do danych hosta. W tym samym czasie klient może poprosić o wyłączny dostęp, aby żaden inny klient ani host nie mógł modyfikować danych hosta.
Jak to zrobiłem, stworzyłem strukturę:
Każdy klient miałby członka takiego:
Wtedy host miałby element slaby_ptr na wyłączność i listę slabych_ptrs dla blokad niewyłącznych:
Dostępna jest funkcja umożliwiająca blokowanie i inna funkcja sprawdzająca, czy host jest zablokowany:
Testuję blokady w LockUpdate, IsUpdateLocked i okresowo w ramach procedury aktualizacji hosta. Testowanie blokady jest tak proste, jak sprawdzenie, czy słaby_ptr wygasł i usunięcie dowolnego wygasłego z listy m_locks (robię to tylko podczas aktualizacji hosta). Mogę sprawdzić, czy lista jest pusta; w tym samym czasie otrzymuję automatyczne odblokowanie, gdy klient resetuje shared_ptr, na którym się trzyma, co również ma miejsce, gdy klient zostaje automatycznie zniszczony.
Ogólny efekt jest taki, że ponieważ klienci rzadko potrzebują wyłączności (zwykle zarezerwowanej tylko dla dodatków i usunięć), w większości przypadków żądanie LockUpdate (false), to znaczy niewyłączne, kończy się sukcesem, o ile (! M_exclusiveLock). LockUpdate (true), żądanie wyłączności, kończy się sukcesem tylko wtedy, gdy zarówno (! M_exclusiveLock), jak i (m_locks.empty ()).
Można dodać kolejkę, aby złagodzić blokady między wyłącznymi i niewyłącznymi blokadami, jednak do tej pory nie miałem żadnych kolizji, więc zamierzam poczekać, aż to się stanie, aby dodać rozwiązanie (głównie dlatego, że mam rzeczywisty warunek testu).
Jak dotąd działa to dobrze na moje potrzeby; Mogę sobie wyobrazić potrzebę rozszerzenia tego i pewne problemy, które mogą pojawić się podczas rozszerzonego użytkowania, jednak było to szybkie do wdrożenia i wymagało bardzo mało niestandardowego kodu.
źródło
Jeśli ktoś jest zainteresowany wersją atomową, oto implementacja. Oczekuje się, że wydajność będzie lepsza niż wersja ze zmiennymi muteksami i warunkami.
źródło
wait
kod musi się zapętlić kilka razy. Kiedy w końcu się odblokuje, zajmie matkę wszystkich błędnie przewidzianych gałęzi, ponieważ przewidywanie pętli procesora z pewnością przewiduje, że zapętli się ponownie. Mógłbym wymienić wiele innych problemów z tym kodem.wait
pętla zużywa zasoby mikrowykonań procesora podczas obracania się. Załóżmy, że znajduje się w tym samym fizycznym rdzeniu, co wątek, który donotify
niego należy - to strasznie spowolni ten wątek.wait
pętli dla tego samego semafora. Obaj piszą z pełną prędkością do tej samej linii pamięci podręcznej, co może spowolnić inne rdzenie do indeksowania poprzez nasycenie magistrali między rdzeniami.