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?
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. –