Proszę wyjaśnić z perspektywy Linuksa, Windowsa?
Programuję w C #, czy te dwa terminy będą miały znaczenie. Publikuj tak dużo, jak możesz, z przykładami i tym podobnymi ....
Dzięki
Proszę wyjaśnić z perspektywy Linuksa, Windowsa?
Programuję w C #, czy te dwa terminy będą miały znaczenie. Publikuj tak dużo, jak możesz, z przykładami i tym podobnymi ....
Dzięki
Odpowiedzi:
W przypadku systemu Windows krytyczne sekcje są lżejsze niż muteksy.
Muteksy mogą być współużytkowane między procesami, ale zawsze powodują wywołanie systemowe jądra, które ma pewien narzut.
Sekcje krytyczne mogą być używane tylko w ramach jednego procesu, ale mają tę zaletę, że przełączają się w tryb jądra tylko w przypadku rywalizacji - niezakończone pozyskiwanie, które powinno być powszechnym przypadkiem, jest niesamowicie szybkie. W przypadku rywalizacji, wchodzą do jądra, aby czekać na jakiś element prymitywny synchronizacji (taki jak zdarzenie lub semafor).
Napisałem krótką przykładową aplikację, która porównuje czas między nimi. W moim systemie dla 1 000 000 niezakłóconych zakupów i wydań mutex zajmuje ponad jedną sekundę. Sekcja krytyczna trwa ~ 50 ms dla 1 000 000 akwizycji.
Oto kod testowy, uruchomiłem go i uzyskałem podobne wyniki, jeśli mutex jest pierwszy lub drugi, więc nie widzimy żadnych innych efektów.
HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
CRITICAL_SECTION critSec;
InitializeCriticalSection(&critSec);
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
LARGE_INTEGER start, end;
// Force code into memory, so we don't see any effects of paging.
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
}
QueryPerformanceCounter(&end);
int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);
// Force code into memory, so we don't see any effects of paging.
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);
}
QueryPerformanceCounter(&end);
int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);
printf("Mutex: %d CritSec: %d\n", totalTime, totalTimeCS);
Z teoretycznego punktu widzenia, sekcja krytyczna to fragment kodu, który nie może być uruchamiany jednocześnie przez wiele wątków, ponieważ kod ma dostęp do współdzielonych zasobów.
Muteks jest algorytmem (czasem nazwę strukturze danych), która służy do zabezpieczenia sekcji krytycznych.
Semafory i monitory są typowymi implementacjami muteksu.
W praktyce w systemie Windows dostępnych jest wiele implementacji mutexów. Różnią się one głównie w wyniku ich implementacji poziomem blokowania, zakresem, kosztami i wydajnością na różnych poziomach rywalizacji. Zobacz CLR Inside Out - using Concurrency for skalowalność, aby zapoznać się z wykresem kosztów różnych implementacji mutex.
Dostępne prymitywy synchronizacji.
lock(object)
Oświadczenie jest realizowany za pomocą Monitor
- patrz MSDN odniesienia.
W ostatnich latach prowadzono wiele badań nad synchronizacją nieblokującą . Celem jest zaimplementowanie algorytmów bez blokowania lub oczekiwania. W takich algorytmach proces pomaga innym procesom zakończyć pracę, aby proces mógł ostatecznie zakończyć swoją pracę. W konsekwencji proces może zakończyć swoją pracę nawet wtedy, gdy inne procesy, które próbowały wykonać jakąś pracę, zawieszają się. Używając blokad, nie zwalnialiby swoich blokad i nie zapobiegaliby kontynuacji innych procesów.
Oprócz innych odpowiedzi poniższe szczegóły dotyczą krytycznych sekcji w systemie Windows:
InterlockedCompareExchange
operacjaW Linuksie myślę, że mają "blokadę spinu", która służy podobnemu celowi jak sekcja krytyczna z liczbą obrotów.
Sekcja krytyczna i Mutex nie są specyficzne dla systemu operacyjnego, ich koncepcje wielowątkowości / wieloprocesorowości.
Sekcja krytyczna to fragment kodu, który musi być uruchamiany samodzielnie w danym momencie (na przykład jednocześnie działa 5 wątków i funkcja o nazwie „krytyczna_sekcja_funkcja”, która aktualizuje tablicę ... nie chcesz wszystkich 5 wątków aktualizowanie tablicy na raz. Tak więc, gdy program wykonuje krytyczną_sekcję_funkcję (), żaden z pozostałych wątków nie może uruchamiać swojej funkcji krytycznej_sekcji.
mutex * Mutex to sposób implementacji kodu sekcji krytycznej (myśl o nim jak o token ... wątek musi go posiadać, aby uruchomić kod krytycznej sekcji)
Muteks to obiekt, który może uzyskać wątek, uniemożliwiając innym wątkom uzyskanie go. Ma charakter doradczy, a nie obowiązkowy; Wątek może używać zasobu reprezentowanego przez muteks bez jego uzyskiwania.
Sekcja krytyczna to długość kodu, której system operacyjny gwarantuje, że nie zostanie przerwany. W pseudokodzie wyglądałoby to tak:
StartCriticalSection();
DoSomethingImportant();
DoSomeOtherImportantThing();
EndCriticalSection();
„Szybki” Windows równy krytycznej selekcji w Linuksie byłby futexem , co oznacza szybki muteks przestrzeni użytkownika. Różnica między futexem a muteksem polega na tym, że w przypadku futexa jądro jest zaangażowane tylko wtedy, gdy wymagany jest arbitraż, więc oszczędzasz na kosztach rozmowy z jądrem za każdym razem, gdy licznik atomowy jest modyfikowany. To .. może zaoszczędzić znaczną ilość czasu podczas negocjowania blokad w niektórych aplikacjach.
Futex może być również współdzielony między procesami, przy użyciu środków, których użyłbyś do współdzielenia muteksu.
Niestety, futexy mogą być bardzo trudne do wdrożenia (PDF). (Aktualizacja 2018, nie są tak przerażające jak w 2009).
Poza tym jest prawie taki sam na obu platformach. Dokonujesz atomowych, opartych na tokenach aktualizacji wspólnej struktury w sposób, który (miejmy nadzieję) nie spowoduje głodu. Pozostaje po prostu metoda osiągnięcia tego.
Aby dodać moje 2 centy, sekcje krytyczne są zdefiniowane jako struktura, a operacje na nich są wykonywane w kontekście trybu użytkownika.
ntdll! _RTL_CRITICAL_SECTION + 0x000 DebugInfo: Ptr32 _RTL_CRITICAL_SECTION_DEBUG + 0x004 LockCount: Int4B + 0x008 RecursionCount: Int4B + 0x00c OwningThread: Ptr32 Void + 0x010 LockSemaphore: Ptr32 Void + 0x014 SpinCount: Uint4B
Natomiast mutex to obiekty jądra (ExMutantObjectType) utworzone w katalogu obiektów systemu Windows. Operacje mutex są najczęściej implementowane w trybie jądra. Na przykład, tworząc Mutex, w końcu wywołujesz w jądrze nt! NtCreateMutant.
Świetna odpowiedź od Michaela. Dodałem trzeci test dla klasy mutex wprowadzonej w C ++ 11. Wynik jest dość interesujący i nadal wspiera jego oryginalne poparcie dla obiektów CRITICAL_SECTION dla pojedynczych procesów.
mutex m;
HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
CRITICAL_SECTION critSec;
InitializeCriticalSection(&critSec);
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
LARGE_INTEGER start, end;
// Force code into memory, so we don't see any effects of paging.
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
}
QueryPerformanceCounter(&end);
int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);
// Force code into memory, so we don't see any effects of paging.
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);
}
QueryPerformanceCounter(&end);
int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);
// Force code into memory, so we don't see any effects of paging.
m.lock();
m.unlock();
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
m.lock();
m.unlock();
}
QueryPerformanceCounter(&end);
int totalTimeM = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);
printf("C++ Mutex: %d Mutex: %d CritSec: %d\n", totalTimeM, totalTime, totalTimeCS);
Moje wyniki to 217, 473 i 19 (zauważ, że mój stosunek czasów dla ostatnich dwóch jest z grubsza porównywalny z Michaelem, ale moja maszyna jest co najmniej cztery lata młodsza od jego, więc możesz zobaczyć dowody na zwiększoną prędkość między 2009 a 2013 , kiedy wyszedł XPS-8700). Nowa klasa mutex jest dwukrotnie szybsza niż mutex systemu Windows, ale wciąż jest mniejsza niż jedna dziesiąta szybkości obiektu CRITICAL_SECTION systemu Windows. Zauważ, że przetestowałem tylko nierekurencyjny mutex. Obiekty CRITICAL_SECTION są rekurencyjne (jeden wątek może je wprowadzać wielokrotnie, pod warunkiem, że opuszcza tę samą liczbę razy).
Funkcje AC są nazywane reentrantem, jeśli używa tylko swoich rzeczywistych parametrów.
Funkcje ponownego wchodzenia mogą być wywoływane jednocześnie przez wiele wątków.
Przykład funkcji ponownego wejścia:
int reentrant_function (int a, int b)
{
int c;
c = a + b;
return c;
}
Przykład funkcji niewracającej ponownie:
int result;
void non_reentrant_function (int a, int b)
{
int c;
c = a + b;
result = c;
}
Standardowa biblioteka C strtok () nie jest ponownie wprowadzana i nie może być używana przez 2 lub więcej wątków w tym samym czasie.
Niektóre platformy SDK są dostarczane z ponownie wprowadzaną wersją strtok () o nazwie strtok_r ();
Enrico Migliore