Sun
21
Dec 2008
Kontynuując temat Programowanie równoległe #1... Czego można się uczyć dalej? Ostatnio czytałem sobie o funkcjach typu InterlockedIncrement czy InterlockedCompareExhange. Wygląda na to, że na niższym poziomie synchronizacja sprowadza się do dwóch problemów - do zapewnienia, że dana operacja jest atomowa oraz do wymuszenia pożądanej kolejności operacji na pamięci (plus synchronizacja cache).
Atomowy w programie 32-bitowym jest zapis i odczyt wyrównanej wartości 32-bitowej. Atomowe są operacje wykonywane przez funkcje WinAPI Interlocked* czy też funkcje Compiler Intrinsics - _Interlocked*. Atomowość zapewniamy też stosując muteksy (sekcje krytyczne).
Z kolei co do pamięci, tutaj pojawiają się takie problemy: zarówno kompilator podczas generowania kodu maszynowego, jak i procesor podczas jego wykonywania może przestawiać kolejność operacji używających dostępu do pamięci. Żeby temu zapobiec, trzeba używać tzw. barier (Memory Barrier). Są trzy rodzaje - o semantice Acquire, Release lub obydwie jednocześnie. Można je wykonywać funkcjami Intrinsic - _ReadBarrier, _WriteBarrier, _ReadWriteBarrier. Systemowe obiekty synchronizujące oraz funkcje Interlocked automatycznie wykonują barierę (choć na innych platformach wcale tak być nie musi).
Ciekawie w tej sytuacji wygląda kwestia słowa kluczowego volatile. Ogólnie pisząc, jego użycie nie zapewnia bezpieczeństwa. Ono powoduje tylko, że kompilator za każdym razem sięga po wartość w pamięci zamiast buforować ją w rejestrach, a począwszy od Visual C++ 2005 dodatkowo wstawia barierę pamięciową (odpowiednio, przy odczycie taką o semantyce Acquire, a przy zapisie taką o semantyce Release).
Tych "niskopoziomowych" mechanizmów używa się do realizacji algorytmów tzw. lock-free (lockless, Non-blocking synchronization czy jak tam to jeszcze inaczej nazwać). Z tych z kolei można budować różne struktury danych. Ten temat, jak również temat tworzenia jednych obiektów synchronizujących za pomocą innych, postaram się opisać wkrótce...