Code review: synchronizacja danych, przypisanie

Często można usłyszeć, że przypisania są zawsze bezpieczne w wielowątkowości i powinniśmy martwić się np. inkrementacją. Jest to prawda dla Int32 ale dla long już nie zawsze. Przykład:

internal class Program
{
   private static long _x = 0;
   private static void Main(string[] args)
   {
       Task.Factory.StartNew(Task1);
       Task.Factory.StartNew(Task2);
       Thread.Sleep(5000);

   }
   private static void Task2()
   {
       while (true)
       {
           Console.WriteLine(_x);
       }
   }
   private static void Task1()
   {
       while (true)
       {
           if (_x == 0)
               _x = long.MaxValue;
           else
               _x = 0;
       }
   }
}

W kodzie mamy zwykle przypisania. Spodziewalibyśmy się, że na ekranie będzie zawsze 0 albo long.MaxValue – w końcu żadnej innej wartości nie ustawiamy tutaj. Jeśli uruchomimy powyższy kod w trybie x86 otrzymamy:

image

Dlaczego na ekranie mamy tak różne wartości? Operacje na Int64  są bezpieczne wyłącznie, jeśli kod jest skompilowany i wykonany na procesor x64. Dla x86 są one rozdzielane na dwie operacje, które ustawiają najpierw 32 bitów i potem w drugiej operacji kolejne bity. Kompilując ten sam kod pod x64 dostaniemy oczekiwane rezultaty:

image

Projektując kod należy mieć to na uwadze bo np. identyfikatory często są typu long i nie wszyscy zdają sobie sprawę, że taki kod może być ryzykowny. Powyższe zachowanie nazywa się “torn reads” ponieważ czytamy tak naprawdę tylko częściowe wyniki.

2 thoughts on “Code review: synchronizacja danych, przypisanie”

  1. Dzięki za wpis! Odkrył bym to zapewne dopiero jak bym się nadział.

    Czy istnieje zatem jakieś zabezpieczenie jak już musimy użyć Int64 na x86?

Leave a Reply

Your email address will not be published.