referat Sebastiana Muszalskiego o synchronizacji
Transkrypt
referat Sebastiana Muszalskiego o synchronizacji
Muszalski Sebastian Synchronizacja, czyli co, jak i dlaczego. 1 W niniejszym referacie chciałbym przybliżyć problem synchronizacji i mam szczerą nadzieję, że mi się to uda. Zanim jednak przejdę do sedna, trzeba zastanowić się czym tak właściwie jest synchronizacja, ponieważ definicji tego określenia jest naprawdę wiele i wszystkie zawierają ziarno prawdy. Samo słowo synchronizacja pochodzi ze starogreckich słów σύν (syn) oznaczającego obok/razem, oraz słowa χρόνος (chronos) - czas. Innymi słowy jest to współdziałanie w tym samym czasie. W zależności od tego, o jakiej dziedzinie mówimy, synchronizacja przybiera nieco inny kształt. Na przykład w kinematografii dość istotną rolę odgrywa synchronizacja filmu oraz dźwięku, często również dołącza do tego synchronizacja napisów. W informatyce synchronizacja to współdziałanie procesów oraz dostęp do tych samych danych przez kilka programów. Zacznijmy od synchronizacji wątków. Synchronizacja wątków jest mechanizmem, który zapewnia, że krytyczne sekcje kodu, czyli fragmenty kody odwołujące się do współdzielonej z innymi procesami pamięci, są wykonywane tylko przez jeden wątek w danym czasie, pozostałe wątki próbujące uzyskać dostęp do tych danych w tym czasie są wstrzymywane. Jeśli w kodzie programu nie zastosujemy odpowiednich zabezpieczeń, może dojść do tzw. race condition, czyli "wyścig" wątków, który polega na występowaniu zdarzeń zależnych od czasu. Może on spowodować zmiany w danych globalnych, które stają się nieprzewidywalne, co z kolei prowadzi do błędnych wyników. Trochę enigmatycznie wygląda to tłumaczenie, dlatego prosty przykład może rozwiać wszelkie wątpliwości. Weźmy licznik wywołań globalnie dostępny i dwa równoległe procesy zwiększające go o 1 (Tabela 1). PROCES P1 P1 P2 P1 P2 P2 DZIAŁANIE Inicjalizacja licznika ZP1 := LICZNIK ZP1 := ZP1 + 1 ZP2 := LICZNIK LICZNIK := ZP1 ZP2 := ZP2 + 1 LICZNIK := ZP2 LICZNIK 0 0 0 0 1 1 1 ZP1 ZP2 0 1 1 1 1 1 0 0 1 1 Tabela 1. Race condition Jak widać w przykładzie procesy P1 i P2 pobierają dane z tego samego źródła i modyfikują je. W założeniu licznik miał zliczyć wywołania, ale przez brak ograniczeń dostępu, nie spełnia swojej funkcji. Oczekiwanym wynikiem powinno być 2. Żeby poradzić sobie z tym problemem, można ustawić semafor, to znaczy w chwili uzyskania dostępu, powinna zostać zmieniona wartość kontrolująca dostęp do wartości, lub utworzony nowy obiekt (plik/zmienna) blokująca dostęp do danych. Rozwiązanie powyższego problemu może z kolei nasunąć pytanie co zrobić, jak wiele procesów chce uzyskać dostęp do tych samych danych. Tutaj najlepiej jest stworzyć kolejkę procesów w celu zachowania kolejności zgłoszeń i dopuszczania do danych globalnych. A co, jeśli który z procesów wykonać swoje obliczenia szybciej? W takim wypadku możemy nadać 2 każdemu procesowi wagę, która będzie określała, jak wysoko dany proces stoi w hierarchii, by następnie "nadzorca kolejki", czyli program generujący wspomnianą kolejkę, sortował ją względem wagi. Innymi problemami, z którymi musi sobie radzić synchronizacja są: • • • • deadlock, jest to zatrzymanie działania operacji, poprzez niedopełnienie określonego działania przez procesy. Wyjaśnię na przykładzie. Mamy procesy P1 i P2 oraz globalnie dostępne dwa miejsca w pamięci, powiedzmy A i B. Proces P1 ma pobrać informację z B, przetworzyć i zapisać w A, z kolei proces P2 ma pobrać dane z A i po przetworzeniu zapisać w B. Każdy z procesów blokuje tylko jedno miejsce w pamięci, a ponieważ żaden z nich nie może dokończyć działania, zakleszczają się. I to jest deadlock. Do likwidacji tego problemu można skonfigurować proces zarządzający dostępem, w taki sposób, by młodszy lub mniej ważny proces zabijał, a po wykonaniu już odblokowanego procesu, wskrzesił zabity proces; starvation, jest to niemożliwość wykonania operacji przez proces, ze względu na brak dostępu do danych lub do procesora. Najczęściej jest to spowodowane przez niewłaściwe zarządzanie kolejką dostępu i/lub błędami w hierarchii procesów. Tutaj nie ma prostej metody na naprawienie tego problemu, trzeba po prostu wrócić do koncepcji i sprawdzenie hierarchii; priority inversion, jest to zjawisko występujące, gdy proces wykonuje swoje obliczenia przed procesem o wyższym priorytecie wykonania. Załóżmy, że mamy dwa działające procesy N i W, odpowiednio o niskim i wysokim priorytecie. Proces N w trakcie obliczeń wykorzystuje zasób globalny M. W trakcie tych obliczeń, proces W również chce uzyskać dostęp do tego zasobu, już blokowanego przez N, przez co W jest zatrzymane. Dobrze napisany system powinien w tym momencie zatrzymać N, zwolnić zasoby M i dopuścić proces W do działania. Może się jednak zdarzyć, że w trakcie działania procesu N, został wywołany proces średniego priorytetu (S). W takiej sytuacji proces N nie może zostać zakończony lub przerwany, dopóki proces S nie zostanie zakończony. Dopiero po zakończeniu procesu S, proces N zostanie zatrzymany, a zasoby zwolnione, by proces W mógł się wykonać; busy waiting, czyli proces ciągle sprawdzający, czy może uzyskać dostęp do zasobów. Nie jest to co prawda szczególnie poważny problem w porównaniu z powyższymi, ale raczej w systemach operacyjnych programiści starają się go unikać, ze względu na marnowanie mocy obliczeniowej procesora. Zamiast tego czasem stosuje się swego rodzaju trigger, który w chwili zwolnienia zasobów uruchamia proces startujący proces o najwyższym priorytecie w kolejce. Przejdźmy zatem do synchronizacji danych. Synchronizacja danych jest to proces nawiązywania i podtrzymywania dwukierunkowego połączenia pomiędzy źródłem a miejscem docelowym plików, oraz ciągłej harmonizacji w czasie, czyli wzajemne dostosowanie różnych elementów w spójną całość. Można ją podzielić na kilka podtypów, 3 które następnie krótko opiszę, a są to: synchronizacja plików, klastrowy system plików, cache coherence, RAID, replikacja danych oraz journaling. Synchronizacja plików, jest to proces zapewniający, że pliki w różnych lokacjach są aktualne. W tym wypadku rozróżniamy synchronizacje jedno- i dwu-kierunkową. W pierwszym przypadku pliki ze źródła są kopiowane do miejsca docelowego. W drugim przypadku najbardziej aktualny plik jest kopiowany do obu lokalizacji. Synchronizacja plików jest zazwyczaj wykorzystywana do robienia kopi zapasowych. Klastrowy system plików jest to sposób zapisu plików, który utrzymuje dane lub indeksy w jednakowy sposób dla całego obrębu obliczeniowego klastra, którym jest sieć komputerowa współpracująca ze sobą. RAID, czyli Redundant Array of Independent Disks, jest to technologia wirtualizacji pamięci fizycznej, w której kilka dysków jest łączone w jeden większy, a dane są zapisywane w wielu miejscach na przestrzeni połączonych dysków, co zwiększa bezpieczeństwo przed utratą całości danych na skutek awarii jednego z dysków. Cache coherency, czyli przechowywanie w wielu miejscach pamięci podręcznej danych współdziałających procesorów. Cache jest to stosunkowo niewielka pamięć, ale z bardzo szybkim czasem dostępu, gdyż jest to pamięć procesora. Replikacja danych, jest to sposób przechowywania kopii danych na serwerach zsynchronizowanych ze sobą za pośrednictwem najczęściej internetu. Replikację danych można podzielić na trzy główne typy: • replikacja migawkowa, w której rozprowadzane dane pochodzą z określonego momentu; • replikacja transakcyjna, w której dane są rozprowadzane na podstawie logów transakcji; • replikacja łącząca, w której dane są rozprowadzane do innego serwera jak i do klientów. Journaling (księgowanie), jest to technika wykorzystywana przez wiele nowoczesnych systemów plików. System plików oparty o journaling zachowuje ślady zmian, jeszcze nie zapisanych przez główny system plików, w strukturach danych nazywanych dziennikiem. Jest on pomocny w przypadku awarii zasilania, ponieważ pozwala na szybsze włączenie systemu oraz odzyskanie niezapisanych danych. Chciałbym jeszcze krótko omówić wyzwania, z którymi musi liczyć się programista przy tworzeniu aplikacji do synchronizacji danych. Pierwszym pytaniem jakie każdy programista powinien sobie zadać, to jakiego formatu będzie potrzebować. W założeniach dowolnego projektu dane wykorzystywane przez aplikację są proste, ale z rozwojem oprogramowania i funkcjonalności zaczynają pojawiać się coraz bardziej złożone formaty danych, ponieważ nie wszystko da się przedstawić w prosty sposób, co może kosztować wiele nieprzespanych nocy poświęconych na modyfikacje sposobu zsynchronizowania danych i/lub procesów. Dlatego tworząc nawet początkowy szablon dla formatu, warto zatroszczyć się o łatwość w operowaniu i łatwość w edytowaniu go. Kolejnym wyzwaniem jest praca w czasie rzeczywistym. W dzisiejszych czasach każdy chce widzieć najbardziej aktualne wiadomości, aktualny status zamówienia, przesyłki etc., zatem programista 4 powinien zatroszczyć się o zapewnienie szybkiego i prostego przekazywania danych, w tym zautomatyzowanie niektórych funkcji (np. automatyczne zamówienie materiału w firmie, gdy jest na wyczerpaniu). Kolejnym dość istotnym problemem jest bezpieczeństwo danych. W tym wypadku, oprócz ogólnie znanego problemu poufności danych, trzeba zwrócić uwagę na możliwe wycieki pamięci, które mogą zakłócić działanie synchronizacji, a nawet doprowadzić do zakleszczenia, które bardziej szczegółowo omówiłem powyżej. Jakość danych. W tym wypadku nie jestem pewien czy istnieje jedyna uniwersalna prawda dotycząca jakości, ponieważ każdy określa ją inaczej. Jednak wspólną ideą zachowania dobrej jakości danych jest nie udostępnianie jej. W taki razie po co tworzyć czy przetwarzać dane, skoro mają być tylko dla mnie? Otóż chodzi tutaj o chowanie źródła, a udostępnianie kopii znajdującej się w innej lokalizacji. W ten sposób zachowujemy dane "świeże i niezmienione", chyba że sami je zmienimy. Również łatwiej jest zarządzać danymi, jeśli dostęp jest ograniczony do nas samych. Ostatnim wyzwaniem, o których chcę wspomnieć to wydajność. Pisząc małe, drobne programy, nawet nie zauważamy, jak wiele czasu i pracy marnujemy. Jednak jeśli budujemy poważną aplikację czy system, każde niepotrzebne działanie może powodować odczuwalne opóźnienia w działaniu, spowodowane przez kiepską synchronizacje procesów lub nieadekwatne nadawanie priorytetu procesom. Istnieją cztery różne etapy synchronizacji wpływające na wydajność: pobranie/wydobycie danych z systemu, transfer danych, operacje na danych i wreszcie załadowanie danych do systemu docelowego. Wszystkie te części są sekcjami krytycznymi, dlatego trzeba starannie zaplanować wykonanie tych części, by nie zaszkodzić wydajności systemu. Mam nadzieję, że udało mi się wyjaśnić czym jest i na co należy zwrócić uwagę przy tworzeniu aplikacji opartych o synchronizację. Jak widać, pracy trzeba włożyć sporo, na wiele rzeczy uważać, ale warto, ponieważ dobrze synchronizowane aplikacje działają znacznie szybciej i wydajniej. 5