2011-08-29 11 views
33

C# nie pozwala na blokowanie wartości pustej. Przypuszczam, że mógłbym sprawdzić, czy wartość jest zerowa, czy nie, zanim ją zablokuję, ale ponieważ nie zablokowałem jej, może pojawić się kolejny wątek i wartość zerowa! Jak mogę uniknąć tego wyścigu?Dlaczego C# nie pozwala na zablokowanie wartości pustej?

+4

Dlaczego nie można po prostu użyć człon że zainicjowany statycznie i * nie zawsze jest null * – zerkms

+2

Jak rozumiem, null jest w zasadzie nic. Jak możesz zablokować nic? Innymi słowy, ciąg myString = null deklaruje zmienną typu string, ale to wszystko jest do niego - nie istnieje jako obiekt, ponieważ nie ma wartości. – Tim

Odpowiedz

23

Zablokuj wartość, która nigdy nie jest pusta, np.

Object _lockOnMe = new Object(); 
Object _iMightBeNull; 
public void DoSomeKungFu() { 
    if (_iMightBeNull == null) { 
     lock (_lockOnMe) { 
      if (_iMightBeNull == null) { 
       _iMightBeNull = ... whatever ...; 
      } 
     } 
    } 
} 

Należy także uważać, aby uniknąć tego ciekawą sytuację wyścigu ze Blokada z podwójnym zatwierdzeniem: Memory Model Guarantees in Double-checked Locking

+3

Może być miło dodać tylko do obiektu blokady, aby wywołać żądaną niezmienność – cordialgerm

+0

, dlaczego blokowanie _lockOnMe może uniemożliwić innym dostęp do _iMightBeNull? –

+0

-1: Twój kod jest nadal podatny na warunki wyścigu, z którymi się łączyłeś; '_iMightBeNull' musi być zadeklarowany jako zmienny. Lub, najlepiej, należy po prostu użyć 'Lazy ' do leniwej inicjalizacji. – Douglas

5

Istnieją dwa problemy tutaj:

Po pierwsze, nie blokuj na null obiektu. To nie ma sensu, jak można rozróżnić dwa obiekty, zarówno te, jak i obiekty?

drugie, aby bezpiecznie zainicjalizować zmienną w środowisku wielowątkowym, użyj dwukrotnie sprawdzane wzór blokowania:

if (o == null) { 
    lock (lockObj) { 
     if (o == null) { 
      o = new Object(); 
     } 
    } 
} 

To zapewni, że inny wątek nie został już zainicjowany obiekt i mogą być wykorzystane do wdrożenia Wzór Singleton.

48

Nie można zablokować na wartość null, ponieważ CLR nie ma miejsca do zamocowania SyncBlock się, co jest, co pozwala CLR do synchronizowania dostępu do dowolnych obiektów poprzez Monitor.Enter/Exit (czego lock używa wewnętrznie)

+0

+1: To jest najbardziej poprawna odpowiedź. –

1

Dlaczego C# nie pozwala na zablokowanie wartości pustej?

Paul's answer to jedyny poprawny technicznie do tej pory, więc zaakceptowałbym to. Dzieje się tak dlatego, że monitory w .NET używają bloku synchronizacji, który jest dołączony do wszystkich typów referencji. Jeśli masz zmienną, która jest null, to nie odnosi się ona do żadnego obiektu, a to oznacza, że ​​monitor nie ma dostępu do użytecznego bloku synchronizacji.

Jak mogę uniknąć tego stanu wyścigu?

Tradycyjne podejście jest, aby zablokować na odwołania do obiektu, który wiesz, nigdy nie będzie null. Jeśli znajdziesz się w sytuacji, w której nie można tego zagwarantować, sklasyfikuję to jako podejście nietradycyjne. Naprawdę nie ma o wiele więcej, o czym mogę tutaj wspomnieć, chyba że opiszę bardziej szczegółowo konkretny scenariusz, który może prowadzić do zerowych celów blokowania.

1

Pierwsza część pytania została już odebrana, ale chciałbym dodać coś do drugiej części pytania.

Łatwiej jest użyć innego obiektu do wykonania blokady, szczególnie w tym stanie. Rozwiązuje to również problem utrzymywania stanów wielu obiektów udostępnionych w sekcji krytycznej, np. lista pracowników i lista zdjęć pracowników.

Ponadto technika ta jest również przydatna, gdy trzeba nabyć blokadę na prymitywnych typów np int lub dziesiętny itp

Moim zdaniem jeśli użyć tej techniki jak wszyscy inni sugerowane wtedy nie trzeba wykonywać zerowa kontrola dwa razy. na przykładw zaakceptowanej odpowiedzi Cris użył warunku dwa razy, który tak naprawdę nie robi żadnej różnicy, ponieważ zablokowany obiekt jest inny niż to, co jest aktualnie modyfikowane, jeśli blokujesz inny obiekt, to wykonanie pierwszego zerowego czeku jest bezużyteczne i marnowanie procesora .

Proponuję następujący fragment kodu;

object readonly syncRootEmployee = new object(); 

List<Employee> employeeList = null; 
List<EmployeePhoto> employeePhotoList = null; 

public void AddEmployee(Employee employee, List<EmployeePhoto> photos) 
{ 
    lock (syncRootEmployee) 
    { 
     if (employeeList == null) 
     { 
      employeeList = new List<Employee>(); 
     } 

     if (employeePhotoList == null) 
     { 
      employeePhotoList = new List<EmployeePhoto>(); 
     } 

     employeeList.Add(employee); 
     foreach(EmployeePhoto ep in photos) 
     { 
      employeePhotoList.Add(ep); 
     } 
    } 
} 

Nie widzę tutaj żadnego stanu wyścigu, jeśli ktoś inny zobaczy stan wyścigu, proszę o odpowiedź w komentarzach. Jak widać w powyższym kodzie, rozwiązuje on 3 problem naraz, jeden nie wymaga kontroli zerowej przed blokowaniem, drugi tworzy sekcję krytyczną bez blokowania dwóch współdzielonych źródeł, a trzecie blokowanie więcej niż jednego obiektu powoduje zakleszczenia z powodu braku uwagi podczas pisania kod.

Oto jak używam zamków w typach pierwotnych.

object readonly syncRootIteration = new object(); 

long iterationCount = 0; 
long iterationTimeMs = 0; 

public void IncrementIterationCount(long timeTook) 
{ 
    lock (syncRootIteration) 
    { 
     iterationCount++; 
     iterationTimeMs = timeTook; 
    } 
} 

public long GetIterationAvgTimeMs() 
{ 
    long result = 0; 

    //if read without lock the result might not be accurate 
    lock (syncRootIteration) 
    { 
     if (this.iterationCount > 0) 
     { 
      result = this.iterationTimeMs/this.iterationCount; 
     } 
    } 

    return result; 
} 

Szczęśliwy gwintowania :)

Powiązane problemy