Synchronizacja za pomocą SpinLock
Posted May 18th, 2012 by Piotr ZielińskiCategories: C#, Wielowątkowość
W .NET istnieje wiele sposobów synchronizacji pracy wątków. O dużej części z nich pisałem już na blogu (z ciekawszych np. klasa Barrier ). Najpopularniejszym i najłatwiejszym sposobem jest użycie słowa kluczowego lock. W wielu przypadkach jest to najlepszy i najbezpieczniejszy wybór. SpinLock to zupełnie inne podejście.
W przypadku lock, wątek jest usypiany i budzony gdy przyjdzie na niego kolej. Ma to kilka poważnych wad. Wiążą się one z szeregowaniem oraz zmianą kontekstu. Zmiana kontekstu jest dość czasochłonna ponieważ należy zapisać stan CPU (rejestry itp.) Usypianie więc wątku jest dość skomplikowane ponieważ należy dokonać pewnego rodzaju serializacji.
SpinLock działa zupełnie inaczej – wątek nigdy nie jest usypiany. Po prostu wątek działa i odpytuje czy może już uzyskać dostęp. Jeśli tak to od razu wykonuje operacje bez zbędnego szeregowania czy zmiany kontekstu. SpinLock to bardzo prosty algorytm, wykonujący pętle i sprawdzający jakąś flagę. W pseudokodzie można to zapisać następująco:
while (IsLockAlreadyTaken) { // do nothing }
Rozważmy więc klasyczny problem zwiększania pewnej współdzielonej zmiennej:
internal class Program { private static int _counter; private static SpinLock _spinLock=new SpinLock(); private static void Main(string[] args) { for (int i = 0; i < 10000; i++) { var task = new Task(IncreaseValue); task.Start(); } Thread.Sleep(7000); Console.WriteLine(_counter); } private static void IncreaseValue() { bool lockTaken = false; try { _spinLock.Enter(ref lockTaken); _counter++; } finally { if (lockTaken) _spinLock.Exit(); } } }
Korzystanie z SpinLock jest łatwe, wystarczy wywołać metodę Enter a potem Exit. Należy jednak zwrócić uwagę na kilka niesłychanie ważnych szczegółów:
- SpinLock to struktura a nie klasa! Z tego względu jeśli chcemy przekazać jako parametr musimy zrobić to przez referencję bo inaczej będą to całkowicie niezależne Spin’y.
- Przed wywołaniem metody SpinLock.Enter flaga musi być ustawiona na false.
- Spinlock nie jest “reentrant” czyli nie można wywołać metody Enter dwa razy. W przypadku gdy jest ustawiona właściwość IsThreadOwnerTrackingEnabled zostanie wyrzucony wyjątek a w przeciwnym wypadku spowoduje to zakleszczenie.
Jakie są wady SpinLock? Największa to fakt, że wątek wciąż zużywa zasoby,wykonuje cykle. Dla operacji krótkotrwałych jest to lepsze niż zmiana kontekstu. W przypadku długich operacji zasoby systemowe są niepotrzebnie zużywane. Należy pamiętać, aby korzystać tylko z SpinLock dla prostych operacji, których wykonanie zajmie kilka cykli CPU – wtedy lepiej poczekać niż zmieniać kontekst. Jeszcze gorszy scenariusz, co w przypadku gdy czekamy na wątek, który został uśpiony ponieważ CPU miał coś ważniejszego do wykonania (interruption)? Im dłuższa operacja tym większe ryzyko. SpinLock jest odradzany dla procesorów jednordzeniowych gdzie trzymanie wątków nic nie robiących jest dużo bardziej kosztowne.



