2010-04-07 10 views
5

Mam system, który pobiera próbki. Mam wiele wątków klienta w aplikacji, które są zainteresowane tymi próbkami, ale rzeczywisty proces pobierania próbki może nastąpić tylko w jednym kontekście. Jest wystarczająco szybki, aby zablokować proces wywoływania, dopóki nie zostanie wykonane próbkowanie, ale na tyle wolny, że nie chcę, aby wiele wątków zgłaszało żądania. Wpadłem z tym wzorem (okrojona do minimalnych informacji):Czy to prawidłowy projekt synchronizacji synchronicznej?

public class Sample 
{ 
    private static Sample _lastSample; 
    private static int _isSampling; 

    public static Sample TakeSample(AutomationManager automation) 
    { 
     //Only start sampling if not already sampling in some other context 
     if (Interlocked.CompareExchange(ref _isSampling, 0, 1) == 0) 
     { 
      try 
      { 
       Sample sample = new Sample(); 
       sample.PerformSampling(automation); 
       _lastSample = sample; 
      } 
      finally 
      { 
       //We're done sampling 
       _isSampling = 0; 
      } 
     } 

     return _lastSample; 
    } 

    private void PerformSampling(AutomationManager automation) 
    { 
     //Lots of stuff going on that shouldn't be run in more than one context at the same time 
    } 
} 

Czy to bezpieczne dla stosowania w scenariuszu opisałem?

Odpowiedz

5

Tak, wygląda bezpiecznie, ponieważ int jest tutaj typem atomowym. Ale chciałbym jeszcze doradzić zastępując

private static int _isSampling; 

z

private static object _samplingLock = new object(); 

i zastosowanie:

lock(_samplingLock) 
{ 
    Sample sample = new Sample(); 
    sample.PerformSampling(automation); 
    _lastSample = sample; 
} 

Po prostu dlatego, że jest zalecany wzór, a także sprawia, że ​​wszystkie dostępu do _lastSample jest traktowane poprawnie.

NB: Spodziewałbym się prędkości porównywalnej, blokada korzysta z zarządzanej klasy monitora, która wewnętrznie korzysta z blokowania.

Edit:

Tęskniłem aspekt back-off, tutaj jest inna wersja:

if (System.Threading.Monitor.TryEnter(_samplingLock)) 
    { 
    try 
    { 
     .... // sample stuff 
    } 
    finally 
    { 
      System.Threading.Monitor.Exit(_samplingLock); 
    } 
    } 
+0

+ 1 dla prostszego rozwiązania. –

+1

TryEnter jest prawdopodobnie lepszy z punktu widzenia konserwacji, ponieważ najprawdopodobniej więcej ludzi będzie znało Monitor.TryEnter niż z Interlocked.CompareExchange. W tym przypadku nie martwię się zbytnio, ponieważ pobieram próbki kilka razy na sekundę. Kiedy faktycznie wykona próbkowanie, na pewno sam proces samplowania będzie nieco większy niż prymityw blokujący. –

+0

Podoba mi się Monitor.TryEnter, ale czy nie powinniśmy używać ReaderWriterLockSlim tych dni? http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim_members.aspx –

-1

zwykle deklarują lotny bool i zrobić coś takiego:

private volatile bool _isBusy; 
private static Sample _lastSample; 

private Sample DoSomething() 
{ 
    lock(_lastSample) 
    { 
     if(_isBusy) 
      return _lastSample; 
     _isBusy = true; 
    } 

    try 
    { 
     _lastSample = new sameple//do something 
    } 
    finally 
    { 
     lock(_lastSample) 
     { 
      _isBusy = false; 
     } 
    } 
    return _lastSample; 
} 
+1

Nie trzeba ogłaszać, że bool jest niestabilny, jeśli mimo wszystko używasz go w zamku. Poza tym, bardzo złą praktyką jest blokowanie zmiennego pola. W twojej próbce możliwe są dwa bloki blokujące w tym samym czasie. Kiedy blokujesz zmienne pole, oczekiwałbym szczegółowego komentarza, dlaczego nie zaszkodzi (tyle), jeśli dwa (lub nawet więcej) wątki wprowadzą blok blokujący jednocześnie. –

Powiązane problemy