2013-02-21 27 views
7

Sytuacja, którą opiszę, występuje na iPadzie 4 (ARMv7s), używając bibliotek POSIX do mutex lock/unlock. Widziałem podobne rzeczy na innych urządzeniach ARMv7 (patrz poniżej), więc przypuszczam, że każde rozwiązanie będzie wymagało bardziej ogólnego spojrzenia na zachowanie muteksów i zapór pamięci dla ARMv7.Czy mutex_unlock działa jako zapora pamięci?

kod Pseudo dla scenariusza:

gwintu 1 - dostarczanie danych:

void ProduceFunction() { 
    MutexLock(); 
    int TempProducerIndex = mSharedProducerIndex; // Take a copy of the int member variable for Producers Index 
    mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index 
    mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable 
    MutexUnlock(); 
} 

Temat 2 - Odbieranie danych:

void ConsumingFunction() { 
    while (mConsumerIndex != mSharedProducerIndex) { 
    doWorkOnData (mSharedArray[mConsumerIndex++]); 
    } 
} 

Wcześniej (gdy problem przycięte na iPadzie 2), wierzyłem, że mSharedProducerIndex = TempProducerIndex nie był perfekcyjny ormedycznie, a więc zmieniono tak, aby użyć AtomicCompareAndSwap do przypisania mSharedProducerIndex. To działało do tego momentu, ale okazało się, że się mylę i błąd powrócił. Myślę, że "poprawka" zmieniła tylko trochę czasu.

I teraz dochodzimy do wniosku, że rzeczywisty problem stanowi wykonywanie poza kolejnością z zapisów w ramach blokady mutex, tzn albo kompilator lub sprzęt postanowił zmienić kolejność:

mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index 
mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable 

.. do:

mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable 
mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index 

... a następnie konsument wymienił producenta, dane nie zostałyby jeszcze zapisane, gdy konsument próbował je odczytać.

Po jakimś czytaniu bariera pamięci, więc ja pomyślałem, że spróbować przenieść sygnał do konsumenta poza mutex_unlock, wierząc, że odblokowanie będzie produkować pamięci Bariera/ogrodzenie, które zapewniłyby mSharedArray został napisany do:

mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index 
MutexUnlock(); 
mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable 

To jednak nadal nie działa, i prowadzi mnie do pytania, czy mutex_unlock pewnością pełnić funkcję zapisu ogrodzenia czy nie?

Przeczytałem również an article from HP, który sugerował, że kompilatory mogą przenosić kod do (ale nie z) crit_sec s. Więc nawet po powyższej zmianie, zapis z mSharedProducerIndex może być przed barierą. Czy istnieje jakaś przebieg tej teorii?

Dodając wyraźnego ogrodzenia problem zniknie:

mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index 
OSMemoryBarrier(); 
mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable 

dlatego myślę, że rozumiem ten problem, i że konieczne jest ogrodzenie, ale każdy wgląd w zachowanie się odblokować i dlaczego to robi” Wygląda na to, że bariera jest naprawdę przydatna.

EDIT:

Odnośnie braku mutex w wątku konsumentów: jestem powołując się na zapis w int mSharedProducerIndex jest pojedyncza instrukcja a zatem nadzieję, że konsument będzie czytać albo nowe lub stare wartości . Oba są poprawnymi stanami i pod warunkiem, że mSharedArray jest zapisany w sekwencji (tj.przed napisaniem mSharedProducerIndex) byłoby to OK, ale z tego, co zostało powiedziane do tej pory, nie mogę odpowiedzieć na to.

Zgodnie z tą samą logiką wydaje się, że obecne rozwiązanie bariery również jest wadliwe, ponieważ zapis mSharedProducerIndex może zostać przesunięty wewnątrz bariery i dlatego może być potencjalnie nieprawidłowo zamówiony ponownie.

Czy zaleca się dodanie muteksu do konsumenta, aby działał jako bariera odczytu, czy istnieje instrukcja wyłączenia wykonywania zleceń poza kolejnością u producenta, np. EIEIO na PPC?

+2

Można po prostu zrobić OSMemoryBarrier() natychmiast po * czytanie * 'mSharedProducerIndex', jak sugeruje Steve. Poprzednie testy wykazały, że OSMemoryBarrier() był nieco szybszy niż OSSpinLockLock + Unlock(), który był znacznie szybszy niż pthread mutexes. –

Odpowiedz

5

Twój produkuje są sync'ed ale nie robić żadnych synchronizacji (trzeba zsynchronizować pamięć z barier, jak również) na czasochłonne. Nawet jeśli masz doskonałe bariery pamięci dla producentów, bariery pamięci nie pomogą konsumentom.

W swoim kodzie możesz zostać trafiony przez zamówienie kompilatora, zamówienie sprzętu nawet przez nieaktualną wartość mSharedProducerIndex na inny rdzeń z wątku # 2.

Powinieneś przeczytać Chapter 11: Memory Ordering z Cortex™-A Series Programmer’s Guide, szczególnie 11.2.1 Memory barrier use example.

Wydaje mi się, że masz problem z otrzymywaniem częściowych aktualizacji w wątku konsumenckim. Problem w tym, co jest w sekcji krytycznej w producencie, nie jest atomowy i można go zmienić.

Przez not atomic Mam na myśli, jeśli twój mSharedArray[TempProducerIndex++] = NewData; nie jest magazynem słów (NewData ma typ int) może to być wykonane w kilku krokach, które mogą być postrzegane przez inne rdzenie jako częściowe aktualizacje.

Przez reordering Mam na myśli, że muteks zapewnia wejścia i wyjścia, ale nie narzuca żadnego porządku podczas sekcji krytycznej. Ponieważ nie masz żadnej specjalnej konstrukcji po stronie konsumenta, możesz zaktualizować mSharedProducerIndex, ale nadal widzisz częściowe aktualizacje do mSharedArray[mConsumerIndex]. Mutex gwarantuje jedynie widoczność pamięci po zakończeniu wykonywania sekcji krytycznej.

Wierzę, że to wyjaśnia również, dlaczego to działa podczas dodawania OSMemoryBarrier(); wewnątrz sekcji krytycznej, bo w ten sposób procesor jest zmuszony do zapisu danych w mSharedArray następnie zaktualizować mConsumerIndex a gdy drugi rdzeń/wątek widzi mConsumerIndex wiemy, że mSharedArray jest w pełni, ponieważ kopiowany bariery.

myślę, że realizacja z OSMemoryBarrier(); jest poprawna zakładając, że masz wiele-producentów i jedna konsumenta. Nie zgadzam się z żadnymi komentarzami sugerującymi umieszczenie bariery pamięci w kliencie, ponieważ uważam, że nie naprawi częściowych aktualizacji lub zmiany kolejności w sekcji krytycznej wewnątrz producenta.

Jako odpowiedź na twoje pytanie w tytule, ogólnie rzecz biorąc, afaik mutex es musi przeczytać barierę przed wejściem do niej i napisać barierę po jej opuszczeniu.

+0

dziękuję za twój wkład do tej pory, dokonałem edycji pytania, które mam nadzieję trochę wyjaśnić. – sjwarner

+0

@sjwarner zaktualizował moją odpowiedź. – auselen

+0

@ sjwarner jeśli mSharedArray nie jest tablicą int, myślę, że nadal możesz mieć problemy z widocznością stąd polecam umieścić barierę odczytu w wątku konsumenta - właśnie na początku pętli. Być może pamięć podręczna jądra jest w trakcie aktualizacji i nie czekałeś na nią - w przeciwieństwie do tego, co robisz. – auselen

5

„Teoria” jest poprawna, pisze mogą być przenoszone z po zapisu ogrodzenia przed nim.

Podstawowym problemem z twoim kodem jest to, że nie ma żadnej synchronizacji w nici 2. Czytasz mSharedProducerIndex bez bariery odczytu, więc kto wie, jaką wartość uzyskasz. Nic, co zrobisz w wątku 1, nie rozwiąże tego problemu.

+0

Przepraszam, uprościłem mój pseudokod, dokonam edycji w ciągu sekundy. Pozdrawiam teraz za odpowiedź. – sjwarner

+0

Edytuj wykonane, mam nadzieję, że wyjaśnia sytuację. Pozdrawiam :) – sjwarner