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