Mariusz Rudnicki mariusz.rudnicki@eti.pg.gda.pl PROGRAMOWANIE WSPÓŁBIEŻNE I SYSTEMY CZASU RZECZYWISTEGO CZ.4
Synchronizacja wątków Omawiane zagadnienia Czym jest synchronizacja wątków? W jakim celu stosuje się mechanizmy synchronizacji? Metody synchronizacji w RT OS: QNX Neutrino, RT Linux, Windows CE. Niebezpieczeństwa związane z synchronizacją. 2/62
Synchronizacja wątków Wątki dostarczają nowych rozwiązań, ale także nowych problemów: Wspólne obszary pamięci: wielokrotne zapisy mogą zamazać oczekiwaną wartość, wątek czytający dane nie wie, kiedy dane są stabilne. Podobne problemy występują z innymi współdzielonymi zasobami: kod programu; zasoby sprzętowe. 3/62
Synchronizacja wątków Problem stanowi synchronizacja pracy wątków. Rozwiązaniem problemu synchronizacji są metody przedstawione w dalszej części prezentacji. Szerzej omawiane: mutexy, zmienne warunkowe, semafory, bariery, joining, operacje atomowe. 4/62
Synchronizacja wątków Pozostałe metody stosowane w QNX Neutrino: rwlocks: pozwala na wielokrotne czytanie bez możliwości zapisu, tylko jeden zapis bez możliwości czytania, jednokrotna inicjalizacja: Pierwsze wywołanie pthread_once z danym argumentem once_control powoduje wykonanie kodu bezargumentowej procedury init_routine i zmienia wartość zmiennej once_control żeby zaznaczyć, ze inicjalizacja była wykonana. Kolejne wywołania pthread_once z tym samym argumentem once_control będą pustymi wywołaniami; 5/62
Synchronizacja wątków Pozostałe metody stosowane w QNX Neutrino: dane własne wątku: Zmienne są powielane dla każdego wątku i każdy może modyfikować swoją kopię zmiennej bez wpływu na inne wątki. 6/62
Synchronizacja wątków słowo kluczowe volatile: zmienne współdzielone: powinny być zadeklarowane jako volatile; volatile unsigned flags;... atomic_clr (&flags, A_FLAG); volatile jest słowem kluczowym ANSI C informującym kompilator żeby nie optymalizował tej zmiennej. przykład: Kompilator może optymalizować kod umieszczając wartości w rejestrze i odnosząc się do rejestru zamiast sięgania do pamięci. Tymczasem inny wątek uzyskiwałby dostęp do pamięci! 7/62
Synchronizacja Mutual Exclusion Mutual exclusion zgodny z POSIX. Wzajemne wykluczenie ang. Mutual exclusion oznacza, że tylko jeden wątek: ma dostęp do krytycznej sekcji kodu w danym momencie; ma dostęp do szczególnego fragmentu danych w danej chwili. 8/62
Synchronizacja Mutual Exclusion Wzajemne wykluczanie projektuje się tak, by zsynchronizować wątki w taki sposób, aby każdy przetwarzał dane i wykonywał sekcję krytyczną w sposób nie kolidujący z wykonaniem sekcji krytycznej innych wątków. Do realizacji tego zagadnienia, należy do funkcji każdego wątku dodać dodatkowe instrukcje poprzedzające i następujące po sekcji krytycznej. 9/62
Synchronizacja Mutual Exclusion API dostarcza następujących wywołań: administracyjnych: pthread_mutex_init (pthread_mutex_t *, pthread_mutexattr_t *); pthread_mutex_destroy (pthread_mutex_t *); użytkowych: pthread_mutex_lock (pthread_mutex_t *); pthread_mutex_trylock (pthread_mutex_t *); pthread_mutex_unlock (pthread_mutex_t *); 10/62
Synchronizacja Mutual Exclusion Przykład: pthread_mutex_t mymutex; init () { domyślne... atrybuty // utworzenie mutexu pthread_mutex_init (&mymutex, NULL);... } thread_func () {... // otrzymaj mutex, czekaj w razie potrzeby pthread_mutex_lock (&mymutex); // operowanie na krytycznych danych // koniec krytycznej sekcji, zwolnij mutex pthread_mutex_unlock (&mymutex);... } cleanup () { pthread_mutex_destroy (&mymutex); } 11/62
Synchronizacja Mutual Exclusion Rozważmy sytuację: freelist Pewna liczba wątków żąda alokacji pamięci wywołując funkcję malloc(). Wewnętrznie, malloc() przechowuje listę wolnych bloków pamięci, które są dostępne do alokacji. Wszystkie wątki w procesie używają tej samej listy. memoryarea 1 memoryarea 2 memoryarea 3 memoryarea 4 NULL 12/62
Synchronizacja Mutual Exclusion W uproszczeniu źródło malloc() wygląda następująco: void * malloc (int nbytes) { while (freelist && freelist -> size!= nbytes) { freelist = freelist -> next; } if (freelist) { // mark block as used, and return block address to caller return (freelist -> memory_block); } } 13/62
Synchronizacja Mutual Exclusion Rozważmy pewną liczbę wątków, które używają malloc(): thread1 () { char *data; } data = malloc (64); thread2 () { char *other_data; } other_data = malloc (64); 14/62
Synchronizacja Mutual Exclusion 15/62
Synchronizacja Mutual Exclusion Problem stanowi rywalizacja wątków o wspólne zasoby, wiele wątków może wchodzić sobie w drogę! Rozwiązaniem problemu jest wyłączny dostęp do struktury danych! Rozwiązanie z użyciem mutexu! 16/62
Synchronizacja Mutual Exclusion Poprawiony kod funkcji malloc(): pthread_mutex_t malloc_mutex; void * malloc (int nbytes) { pthread_mutex_lock (&malloc_mutex); while (freelist && freelist -> size!= nbytes) { freelist = freelist -> next; } if (freelist) { // mark block as used, and return block block = freelist -> memory_block; pthread_mutex_unlock (&malloc_mutex); return (block); } pthread_mutex_unlock (&malloc_mutex); } 17/62
Synchronizacja Mutual Exclusion Aby zainicjować mutex należy wykonać następujący fragment kodu: pthread_mutex_init (&malloc_mutex, NULL); Jeżeli zakończy się powodzeniem gwarantuje, że wszystkie odpowiednie zasoby zostały zaalokowane dla mutexa. 18/62
Synchronizacja Mutual Exclusion Prosta metoda inicjalizacji mutexu: // statyczna inicjalizacja Mutexu pthread_mutex_t malloc_mutex = PTHREAD_MUTEX_INITIALIZER; void * malloc (int nbytes) {... // MUTEX będzie zainicjowany po pierwszym //użyciu pthread_mutex_lock (&malloc_mutex);... 19/62
Synchronizacja Mutual Exclusion Domyślnie mutexy nie mogą być współdzielone między procesami; aby współdzielić mutexa, ustawiamy dla niego flagę PTHREAD_PROCESS_SHARED; mutex powinien być zdefiniowany w pamięci współdzielonej; np.: pthread_mutexattr_t mutex_attr; pthread_mutex_t *mutex; pthread_mutexattr_init( &mutex_attr ); pthread_mutexattr_setpshared( &mutex_attr, PTHREAD_PROCESS_SHARED); mutex = (pthread_mutex_t *)shmem_ptr; pthread_mutex_init( mutex, &mutex_attr ); 20/62
Synchronizacja Mutual Exclusion Pamiętaj! Żaden wątek nie może wykonywać swej sekcji krytycznej nieskończenie długo, nie może się w niej zapętlić lub zakończyć w wyniku jakiegoś błędu; W sekcji krytycznej wątek przebywa jak najkrócej i nie może zakończyć się błędem. 21/62
Synchronizacja Condition variables Rozważmy prosty przypadek, gdzie: musimy zablokować wątek, oczekując aż inny wątek zmieni pewną zmienną: int state; thread_1 (){ } while (1) { } // wait until state changes, // then, perform some work Zmienne warunkowe dostarczają mechanizm oczekiwania na taką zmianę. 22/62
Synchronizacja Condition variables Wywołania: pthread_cond_init (pthread_cond_t *, pthread_condattr_t *); pthread_cond_wait (pthread_cond_t *, pthread_mutex_t *); pthread_cond_signal (pthread_cond_t *); pthread_cond_broadcast (pthread_cond_t *); 23/62
Synchronizacja Condition variables Wywołania: wątek dostarczający dane dostaje pewną ich ilość, np. od procesu klienta i dodaje je do kolejki; powiadamia wątek obsługi sprzętu o nowych danych; wątek obsługi sprzętu budzi się, pobiera dane z kolejki i przesyła je do sprzętu. 24/62
Synchronizacja Condition variables Żeby to wykonać potrzebujemy dwóch rzeczy: mutexa aby zapewnić pojedynczy dostęp do kolejki danych, mechanizmu dla wątku dostarczającego dane by poinformował wątek obsługi sprzętu o nowych danych i obudził go. 25/62
Synchronizacja Condition variables Kod wątku obsługi sprzętu: while (1) { pthread_mutex_lock (&mutex); // wyłączność dostępu if (!data_ready) pthread_cond_wait (&cond, &mutex); // oczekiwanie na dane } /* pobranie danych z kolejki */ while ((data = get_data_and_remove_from_queue ())!= NULL) { pthread_mutex_unlock (&mutex); write_to_hardware (data); // przekaż do urządzenia free (data); // zwolnij zasoby pthread_mutex_lock (&mutex); } data_ready = 0; // ponowne ustawienie flagi pthread_mutex_unlock (&mutex); 26/62
Synchronizacja Condition variables Kod wątku dostarczającego dane: pthread_mutex_lock (&mutex); // wyłączność dostępu add_to_queue (buf); data_ready = 1; // ustawienie flagi pthread_cond_signal (&cond); // powiadomienie oczekującego pthread_mutex_unlock (&mutex); // zwolnienie dostępu 27/62
Synchronizacja Condition variables Przyjrzyjmy się bliżej funkcji wait : pthread_cond_wait (&condvar, &mutex); Zawiesza sekcję krytyczną ustanowioną przez mutex podany jako drugi parametr (dlatego przed wywołaniem tej funkcji wątek powinien ustawić sekcję krytyczną za pomocą mutexa), następnie usypia aktualny wątek. Podczas sukcesu ponownie ustawia sekcję krytyczną i zwraca 0. 28/62
Synchronizacja Condition variables Dlaczego wykonujemy to sprawdzenie? while (1) { } 2 1 pthread_mutex_lock (&mutex); if (data_ready == 0)... jeżeli sygnalizujemy zmianę stanu zmiennej warunkowej i żaden wątek nie czeka na taki sygnał, jest on tracony; sygnał powinien być wysłany pomiędzy 1 i 2 ; // wyłączny dostęp pthread_cond_wait (&cond, &mutex); // oczekujemy data_ready = 0; pthread_mutex_unlock (&mutex); // ponownie ustawiamy flagę proces sygnalizujący ustawia też flagę (data_ready = 1) 29/62
Synchronizacja Condition variables Sygnalizacja vs broadcast: wątki 1, 2 i 3 (wszystkie o takim samym priorytecie) czekają na zmianę używając pthread_cond_wait(), Wątek 4 dokonuje zmiany i sygnalizuje za pomocą pthread_cond_signal(), Najdłużej czekający wątek (powiedzmy 2 ) jest informowany o zmianie i próbuje zablokować mutex ( automatycznie przez pthread_cond_wait()), Wątek 2 sprawdza warunek, przeprowadza działania lub wraca do uśpienia 30/62
Synchronizacja Condition variables Co się dzieje z wątkami 1 i 3? nigdy nie zauważą zmiany! Jeżeli zmienimy przykład: wykorzystamy pthread_cond_broadcast() zamiast pthread_cond_signal(), wtedy wszystkie trzy wątki otrzymają powiadomienie wszystkie wątki zmienią stan na READY, jednak tylko jeden z nich może zablokować mutex jednocześnie - pozostałe kolejno. 31/62
Synchronizacja Condition variables Jaki sposób sygnalizowani wybrać? wybieramy sygnał, jeżeli: mamy jeden oczekujący wątek; potrzebujemy uruchomić tylko jeden wątek do przetwarzania i nie musimy informować pozostałych; używamy broadcast, jeżeli mamy wiele wątków i: wszystkie muszą przeprowadzić działania po dokonanej zmianie; lub nie wszystkie muszą zareagować na zmianę, ale nie wiemy, który z nich obudzić. 32/62
Synchronizacja Condition variables Domyślnie, zmienne warunkowe nie mogą być współdzielone z innymi procesami: aby współdzielić zmienne warunkowe, ustawiamy odpowiednią flagę PTHREAD_PROCESS_SHARED: zmienna warunkowa powinna być w pamięci współdzielonej: pthread_condattr_t cond_attr; pthread_cond_t *cond; pthread_condattr_init( &cond_attr ); pthread_condattr_setpshared( &cond_attr, PTHREAD_PROCESS_SHARED); cond = (pthread_cond_t *)shmem_ptr; pthread_cond_init(cond, &cond_attr ); 33/62
Synchronizacja Condition variables Przykład producenta/konsumenta: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; volatile int state = 0; volatile int product = 0; void *consume (void *arg) { } while (1) { pthread_mutex_lock (&mutex); while (state == 0) { } pthread_cond_wait (&cond, &mutex); printf ( Consumed %d\n, product); state = 0; pthread_cond_signal (&cond); pthread_mutex_unlock (&mutex); do_consumer_work (); } return (0); 34/62
Synchronizacja Condition variables Przykład producenta/konsumenta: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; volatile int state = 0; volatile int product = 0; void *consume (void *arg) { } while (1) { pthread_mutex_lock (&mutex); while (state == 0) { } pthread_cond_wait (&cond, &mutex); printf ( Consumed %d\n, product); state = 0; pthread_cond_signal (&cond); pthread_mutex_unlock (&mutex); do_consumer_work (); } return (0); 35/62
Synchronizacja Condition variables void *produce (void *arg) { } while (1) { } pthread_mutex_lock (&mutex); while (state == 1) { pthread_cond_wait (&cond, &mutex); } printf ( Produced %d\n, product++); state = 1; pthread_cond_signal (&cond); pthread_mutex_unlock (&mutex); do_producer_work (); return (0); int main () { pthread_create (NULL, NULL, &produce, NULL); consume (NULL); return (EXIT_SUCCESS); } 36/62
Synchronizacja Semaphores Semafor - jest obiektem abstrakcyjnym służącym do kontrolowania dostępu do ograniczonego zasobu. Semafory są szczególnie przydatne w środowisku gdzie wiele procesów lub wątków komunikuje się przez wspólną pamięć. 37/62
Synchronizacja Semaphores W standardzie POSIX wyróżnione są dwa typy semaforów: Semafory nienazwane identyfikowane po adresie semafora. Stąd nazwa semafor nienazwany. Semafory nazwane - identyfikowane są w procesach poprzez ich nazwę. Na semaforze nazwanym operuje się tak samo jak na semaforze nienazwanym z wyjątkiem funkcji otwarcia i zamknięcia semafora. 38/62
Synchronizacja Semaphores Wykorzystanie semaforów dla sterowania dostępem administracja: sem_init (sem_t *semaphore, int pshared, unsigned int val); sem_destroy (sem_t *semaphore); unnamed semaphores sem_t *sem_open (char *name, int oflag, [int sharing, unsigned int val]); sem_close (sem_t *semaphore); named semaphores sem_unlink (char *name); użycie: sem_post (sem_t *semaphore); sem_trywait (sem_t *semaphore); sem_wait (sem_t *semaphore); sem_getvalue (sem_t *semaphore, int *value); 39/62
Synchronizacja Semaphores Nienazwane vs nazwane semafory: z nienazwanymi, wywołania sem_post() i sem_wait() powodują bezpośrednie wywołanie jądra związane z semaforem, z nazwanymi, wywołania sem_post() i sem_wait() wysyłają komunikaty do mqueue a stąd generowane są wywołania jądra, semafory nienazwane są szybsze niż nazwane, semafory nazwane wykorzystywane są do komunikacji pomiędzy procesami. 40/62
QNX Neutrino Inne mechanizmy synchronizacji BARRIERS: Bariera jest mechanizmem synchronizującym, który pozwala gromadzić współpracujące ze sobą wątki i zmusić je do oczekiwania w pewnym wyspecyfikowanym punkcie dopóki wszystkie nie wykonają określonych czynności. W przeciwieństwie do mechanizmu joining w przypadku barier oczekujemy, że wątki spotkają się w określonym przez programistę punkcie. Kiedy określona liczba wątków dotrze do bariery, wszystkie zostaną odblokowane i mogą kontynuować pracę. 41/62
QNX Neutrino Inne mechanizmy synchronizacji BARRIERS: Sposób postępowania: Tworzymy obiekt bariery: pthread_barrier_init(); int pthread_barrier_init (pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count); Argument count zawiera liczbę wątków, które muszą osiągnąć barierę; Po wywołaniu pthread_barrier_wait() wątek zostaje zablokowany dopóki określona, w funkcji pthread_barrier_init( ), liczba wątków nie wywoła funkcji pthread_barrier_wait(). 42/62
QNX Neutrino Inne mechanizmy synchronizacji BARRIERS: Kiedy właściwa liczba wątków wywoła funkcję pthread_barrier_wait(), inaczej mówiąc osiągnie barierę, wszystkie zostają kolejno odblokowane. Liczba wątków, które muszą dotrzeć do bariery musi uwzględniać również wątek główny, w którym tworzony i inicjowany jest obiekt bariery. 43/62
QNX Neutrino Inne mechanizmy synchronizacji BARRIERS: Funkcje administrujące: pthread_barrier_init(); pthread_barrierattr_init(); pthread_barrierattr_destroy(); pthread_barrierattr_getpshared(); pthread_barrierattr_setpshared(); pthread_barrier_destroy(). Funkcje użytkowe: pthread_barrier_wait(). 44/62
QNX Neutrino Inne mechanizmy synchronizacji JOINING: Najprostsza metoda synchronizacji oczekiwanie na zakończenie wątku. Rozważmy przykładowy kod: int num_lines_per_cpu, num_cpus; int main (int argc, char **argv){ int cpu; pthread_t *thread_ids;... // perform initializations thread_ids = malloc (sizeof (pthread_t) * num_cpus); 45/62
QNX Neutrino Inne mechanizmy synchronizacji JOINING: num_lines_per_cpu = num_x_lines / num_cpus; for (cpu = 0; cpu < num_cpus; cpu++) { pthread_create (&thread_ids [cpu], NULL, do_one_batch, (void *) cpu); } // synchronize to termination of all threads for (cpu = 0; cpu < num_cpus; cpu++) { pthread_join (thread_ids [cpu], NULL); } }... // display results 46/62
QNX Neutrino Inne mechanizmy synchronizacji JOINING: Funkcja pthread_create() w pierwszym parametrze zwraca adres do zmiennej typu pthread_t. Jest to miejsce, w którym przechowywany jest identyfikator nowo utworzonego wątku. W pierwszej pętli for( ) uruchamiamy num_cpus wątków, plus wątek główny procesu (z funkcją main()). Nie będziemy zajmować się wątkiem głównym, gdyż większość czasu spędzi on na oczekiwaniu. Oczekiwanie zakończy się poprzez wykonanie funkcji pthread_join() kolejno na każdym wątku, czyli sprawdzeniu czy zakończył swoją pracę. 47/62
QNX Neutrino Inne mechanizmy synchronizacji JOINING: W pierwszej kolejności oczekujemy na zakończenie wątku threads_ids[0]. Kiedy skończy on swoją pracę funkcja pthread_join() odblokuje wątek główny. W kolejnej iteracji w pętli for( ) będziemy czekać na zakończenie wątku thread_ids[1], itd. W tym miejscu pojawia się pytanie: Co się stanie gdy wątki zakończą swoją pracę w odwrotnej kolejności? 48/62
QNX Neutrino Inne mechanizmy synchronizacji JOINING: W obecnej sytuacji funkcja pthread_join() blokuje wątek główny na thread_ids[0]. Tymczasem wątek thread_ids[3] zakończył swoją pracę. Sytuacja ta nie ma jednak pływu na wątek główny, który nadal czeka na zakończenie wątku pierwszego. Zakończenie pracy przez wątek thread_ids[2], również nie ma wpływu na wątek główny. Podobnie dzieje się po zakończeniu pracy przez wątek thread_ids[1]. 49/62
QNX Neutrino Inne mechanizmy synchronizacji JOINING: W końcu, gdy wątek pierwszy zakończy swoją pracę wątek główny zostanie natychmiast odblokowany i będzie mógł wykonać następną iterację w pętli for( ). Kolejny wątek już nie pracuje, tak więc wątek główny nie będzie blokowany i będzie mógł wykonać kolejne operacje i zakończyć swoją pracę. 50/62
QNX Neutrino Inne mechanizmy synchronizacji ATOMIC OPERATION: Pewne praktyczne funkcje zdefiniowane w pliku nagłówkowym <atomic.h> pozwalają wykonywać operacje atomowe (operacje, które gwarantują niepodzielność i nieprzerwalność). Operacje te nie mogą zostać wydziedziczone przez inny wątek lub funkcję obsługi przerwania ISR. 51/62
QNX Neutrino Inne mechanizmy synchronizacji ATOMIC OPERATION: Używając tych funkcji łagodzimy potrzebę wyłączania i włączania przerwań na krótki czas wykonywania ściśle określonych operacji na zmiennych, takich jak: Dodawanie, odejmowanie; Ustawianie i czyszczenie bitów; Zmianę wartości bitów. 52/62
QNX Neutrino Inne mechanizmy synchronizacji ATOMIC OPERATION: Operacji atomowych możemy używać wszędzie. Szczególnie przydatne są w operacjach: Pomiędzy ISR i wątkiem; Pomiędzy dwoma wątkami (SMP lub pojedynczy procesor). Operacje atomowe ograniczają konieczność blokowania i ponownego włączania przerwań w systemach czasu rzeczywistego. 53/62
Synchronizacja RT Linux: mutexy, semafory zgodne z POSIX. 54/62
Synchronizacja Windows Embedded obiekty synchronizacji wątków: sekcje krytyczne, mutexy, zdarzenia, semafory, funkcje oczekujące. 55/62
Synchronizacja Windows Embedded sekcje krytyczne: chronią część kodu przed dostępem wielu wątków w jednym czasie, mogą być wykorzystywane w obrębie jednego procesu lub DLL, nie mogą być współdzielone przez inne procesy. void InitializeCriticalSection (LPCRITICAL_SECTION lpcriticalsection); void EnterCriticalSection (LPCRITICAL_SECTION lpcriticalsection); void LeaveCriticalSection (LPCRITICAL_SECTION lpcriticalsection); void DeleteCriticalSection (LPCRITICAL_SECTION lpcriticalsection); 56/62
Synchronizacja Windows Embedded mutexy: są obiektami, których stan sygnalizuje czy są używane przez wątek; koordynują wyłączny dostęp do współdzielonych zasobów; w danym czasie tylko jeden wątek może używać mutexa; mutex może być uważany za porzucony ang. abandoned w sytuacji gdy wątek zakończy pracę i nie zwolni mutexa; wątek oczekujący może uzyskać prawa do mutexa porzuconego; 57/62
Synchronizacja Windows Embedded mutexy: pojawienie się mutexa porzuconego sygnalizuje, że wystąpił błąd w systemie i stan wszelkich zasobów chronionych tym mutexem jest nieokreślony; jeżeli wątek uzna, że mutex nie jest porzucony i kontynuuje pracę to po zwolnieniu mutexa flaga porzucony zostaje skasowana i staje się on zwykłym mutexem; mutexy mogą być używane w synchronizacji pracy wielu procesów. 58/62
Synchronizacja Windows Embedded semafory: są obiektami: synchronizacji wątków wewnątrz jednego procesu, jak również synchronizacji międzyprocesowej; stan semafora jest sygnalizowany, kiedy jego licznik jest większy od zera i nie jest sygnalizowany, gdy licznik równy jest zero; semafor jest bramą użycia zasobu ograniczającą jego użycie poprzez zliczanie wątków przechodzących przez bramę; 59/62
Synchronizacja Windows Embedded semafory: za każdym razem gdy wątek oczekujący jest przepuszczany licznik semafora jest zmniejszana; aplikacja może stworzyć semafor z licznikiem = 0, powoduje to ustawienie semafora w stan niesygnalizowany i blokuje wszystkie wątki żądające dostępu do chronionego zasobu. 60/62
Synchronizacja Windows Embedded zdarzenia: zdarzenia są obiektami wykorzystywanymi do: powiadamiania wątku kiedy ma wykonać swoją pracę; wskazania, że dane zdarzenie miało miejsce; nazwane zdarzenia mogą być wykorzystywane do synchronizacji wątków różnych procesów. 61/62
Synchronizacja Windows Embedded funkcje oczekujące: są obiektami blokującymi lub odblokowującymi wątek, bazującymi na stanie obiektu: oczekiwanie na jeden obiekt WaitForSingleObject; oczekiwanie na wiele obiektów WaitForMultipleObjects; oczekiwanymi obiektami mogą być obiekty synchronizacji np. mutexy lub zdarzenia bądź też uchwyty do procesów lub wątków; funkcje oczekujące na wiele obiektów tworzą tablicę zawierającą jeden lub więcej obiektów synchronizacji np.: stan obiektu synchronizacji; upłynięcie określonego czasu. 62/62