= Operacje atomowe =
== Przedrostek lock ==
Instrukcje asemblera dają możliwość atomowego wykonywania pewnych operacji.
Przydaje się to w systemach wieloprocesorowych do synchronizacji między
procesorami, ale również w niektórych przypadkach do zapewnienia spójności
danych między dwoma procesami, które np. komunikują się przez wspóldzieloną
pamięć. 

I tak, na przykład, operacja dodania 1 do zmiennej może być zaimplementowana
przez kompilator na różne sposoby:
mov rejestr, zmienna
inc rejestr
....
mov zmienna, rejestr
Powyższa implementacja może spowodować błędy w środowisku wieloprocesorowym
lub wieloprogramowym, jeśli operujemy na współdzielonej pamięci i
wykonywanie ciągu instrukcji zostanie przerwane w trakcie.
Można spróbować użyć instrukcji inc:
inc zmienna
Ta implementacja jest prawidłowa w środowisku jednoprocesorowym, ale w
wieloprocesorowym nie - procesor działa w ten sposób, że pobiera wartość
zmiennej, zwiększa ją, a potem odsyła z powrotem do pamięci. 
Drugi procesor może próbować wykonać jakąś inną operację na tej zmiennej w
tym samym czasie i jest podobny problem jak w pierwszym przypadku.

Dlatego do instrukcji można dodać przedrostek "lock" - powoduje on, że na
czas wykonania szyna do pamięci jest zablokowana i nikt nie może
zmodyfikować pamięci za naszymi plecami. Ma to jednak tę wadę, że pozostałe
procesory nie mogą operować na pamięci, więc należy tego unikać.
Prawidłowa implementacja wygląda zatem tak:
lock inc zmienna

Nie wszystkie intrukcje można poprzedzić przedrostkiem lock.
Niektóre się kompilują, ale w trakcie wykonania procesor zgłasza błąd.

Instrukcje z przedrostkiem lock są zazwyczaj opakowywane w makra lub
funkcje, tak by można było ich wygodnie używać - np. w jądrze Linuksa są to
operacje na typie atomic_t, np. atomic_add().

= Instrukcje compare-and-swap =
Przy synchronizacji w systemach wieloprocesorowych często występuje
konieczność warunkowego wykonania jakiejś operacji, w sposób niepodzielny.
W tym celu w procesorach x86 dodano operacje compare-and-swap - ich idea
polega na porównaniu dwóch wartości i jeśli są równe to trzecia wartość jest
kopiowana do pierwszej wartości.
Używa się ich zazwyczaj w ten sposób, że na zmiennej trzyma się wartość
określającą, czy można wejść do sekcji krytycznej (np. 0 - sekcja krytyczna
wolna, 1 - zajęta). Następnie porównuje się zmienną z 0 i jeśli jest równe
0, to ładuje się "1", co oznacza, że sekcja jest zajęta. Jeśli warunek
równości spełniony, to wchodzimy do sekcji krytycznej ustawiając "1", w
przeciwnym wypadku powtarzamy operację.

Instrukcje:
XMPXCHG - wersja 8, 16 lub 32 bitowa (zależnie od rozmiaru argumentu)
CMPXCHG8B - wersja 64-bitowa

Uwaga! Te instrukcje należy w systemach wieloprocesorowych poprzedzić
przedrostkiem lock.

Inne procesory niż x86 mają podobne instrukcje, ich implementacja może się
trochę różnić, np. jest to instrukcja compare-and-set.

= Instrukcja XADD =
Inną atomową instrukcją, która może się przydać przy synchronizacji jest XADD
zamienia ona 2 argumenty, dodaje je, a potem zapisuje sumę do pierwszego
argumentu.

= Struktury lock-free =
Na instrukcjach compare-and-swap można budować struktury, które nie wymagają
użycia lockowania - tzw. struktury lock-free.
Idea jest taka: wykonujemy jakąś operację licząc, że stan struktury się nie
zmieni, kiedy mamy już zatwierdzić operację sprawdzamy i podmieniamy
wskaźnik za pomocą operacji compare-and-swap.

Zadania:
- Sprawdzić, które instrukcje można poprzedzić przedrostkiem lock.
- Zaimplementować spinlock (wirującą blokadę) używając instrukcji
compare-exchange.
- Zaimplementować stos lock-free.
