Wielowątkowość: jak używać klas synchronizacji MFC
Synchronizowanie dostępu do zasobów między wątkami jest typowym problemem podczas pisania aplikacji wielowątkowych. Posiadanie co najmniej dwóch wątków jednocześnie dostępu do tych samych danych może prowadzić do niepożądanych i nieprzewidywalnych wyników. Na przykład jeden wątek może aktualizować zawartość struktury, podczas gdy inny wątek odczytuje zawartość tej samej struktury. Nie wiadomo, jakie dane otrzyma wątek odczytu: stare dane, nowo zapisane dane lub ewentualnie kombinację obu tych elementów. MFC udostępnia szereg klas dostępu do synchronizacji i synchronizacji, które ułatwiają rozwiązywanie tego problemu. W tym temacie opisano dostępne klasy i sposób ich używania do tworzenia klas bezpiecznych wątkowo w typowej aplikacji wielowątku.
Typowa aplikacja wielowątkowa ma klasę reprezentującą zasób, który ma być współużytkowany między wątkami. Prawidłowo zaprojektowana, w pełni bezpieczna wątkowo klasa nie wymaga wywoływania żadnych funkcji synchronizacji. Wszystko jest obsługiwane wewnętrznie w klasie, co pozwala skoncentrować się na tym, jak najlepiej korzystać z klasy, a nie o tym, jak może zostać uszkodzony. Efektywną techniką tworzenia klasy w pełni bezpiecznej wątkowo jest scalenie klasy synchronizacji z klasą zasobów. Scalanie klas synchronizacji z klasą udostępnioną jest prostym procesem.
Na przykład weźmy aplikację, która utrzymuje połączoną listę kont. Ta aplikacja umożliwia przeanalizowanie maksymalnie trzech kont w oddzielnych oknach, ale tylko jedno można zaktualizować w dowolnym momencie. Po zaktualizowaniu konta zaktualizowane dane są wysyłane przez sieć do archiwum danych.
W tej przykładowej aplikacji są używane wszystkie trzy typy klas synchronizacji. Ponieważ pozwala to na jednoczesne badanie maksymalnie trzech kont, używa CSemaphore , aby ograniczyć dostęp do trzech obiektów widoku. Gdy zostanie podjęta próba wyświetlenia czwartego konta, aplikacja zaczeka na zamknięcie jednego z trzech pierwszych okien lub niepowodzenie. Po zaktualizowaniu konta aplikacja używa CCriticalSection , aby upewnić się, że tylko jedno konto jest aktualizowane naraz. Po pomyślnym zakończeniu aktualizacji sygnalizuje ona CEvent, która zwalnia wątek oczekujący na zasygnaliowanie zdarzenia. Ten wątek wysyła nowe dane do archiwum danych.
Projektowanie klasy bezpieczne wątkowo
Aby zapewnić bezpieczeństwo wątków klasy, najpierw dodaj odpowiednią klasę synchronizacji do klas udostępnionych jako składową danych. W poprzednim przykładzie CSemaphore
zarządzania kontami element członkowski danych zostanie dodany do klasy widoku, CCriticalSection
składowy danych zostanie dodany do klasy listy połączonej, a CEvent
składowy danych zostanie dodany do klasy magazynu danych.
Następnie dodaj wywołania synchronizacji do wszystkich funkcji składowych, które modyfikują dane w klasie lub uzyskują dostęp do kontrolowanego zasobu. W każdej funkcji należy utworzyć obiekt CSingleLock lub CMultiLock i wywołać funkcję tego obiektu Lock
. Gdy obiekt blokady wykracza poza zakres i jest niszczony, destruktor obiektu wywołuje Unlock
dla Ciebie, zwalniając zasób. Oczywiście możesz zadzwonić Unlock
bezpośrednio, jeśli chcesz.
Projektowanie klasy bezpiecznej wątkowo w ten sposób pozwala na korzystanie z niej w aplikacji wielowątkowym tak łatwo, jak w przypadku klasy bezpiecznej bez wątków, ale z wyższym poziomem bezpieczeństwa. Hermetyzowanie obiektu synchronizacji i dostępu synchronizacji do klasy zasobu zapewnia wszystkie korzyści z pełnego programowania wątkowego bez wad utrzymania kodu synchronizacji.
Poniższy przykład kodu przedstawia tę metodę przy użyciu składowej m_CritSection
danych (typu CCriticalSection
), zadeklarowanej w udostępnionej klasie zasobów i CSingleLock
obiekcie. Synchronizacja zasobu udostępnionego (pochodzącego z CWinThread
klasy ) jest podejmowana przez utworzenie CSingleLock
obiektu przy użyciu adresu m_CritSection
obiektu. Podjęto próbę zablokowania zasobu, a po uzyskaniu praca jest wykonywana na obiekcie udostępnionym. Po zakończeniu pracy zasób zostanie odblokowany za pomocą wywołania metody Unlock
.
CSingleLock singleLock(&m_CritSection);
singleLock.Lock();
// resource locked
//.usage of shared resource...
singleLock.Unlock();
Uwaga
CCriticalSection
, w przeciwieństwie do innych klas synchronizacji MFC, nie ma opcji żądania blokady czasowej. Okres oczekiwania na uwolnienie wątku jest nieskończony.
Wadą tego podejścia jest to, że klasa będzie nieco wolniejsza niż ta sama klasa bez dodanych obiektów synchronizacji. Ponadto, jeśli istnieje prawdopodobieństwo, że więcej niż jeden wątek może usunąć obiekt, scalone podejście może nie zawsze działać. W takiej sytuacji lepiej zachować oddzielne obiekty synchronizacji.
Aby uzyskać informacje na temat określania klasy synchronizacji do użycia w różnych sytuacjach, zobacz Multithreading: When to Use the Synchronization Classes (Wielowątkowość: kiedy należy używać klas synchronizacji). Aby uzyskać więcej informacji na temat synchronizacji, zobacz Synchronizacja w zestawie Windows SDK. Aby uzyskać więcej informacji na temat obsługi wielowątkowość w MFC, zobacz Multithreading with C++ and MFC (Obsługa wielowątkowość w języku C++ i MFC).