2012-12-26 10 views
6

Uwielbiam szybki sposób tworzenia klas w języku C#, gdzie możemy łatwo tworzyć członków bez konieczności implementowania kodu get i set, ale (o ile mi wiadomo) nie są bezpieczni dla wątków!Jakikolwiek sposób, aby uczynić ten wątek klasy C# bezpiecznym bez przepisywania?

public class SocksEntry 
{ 
    public int Int1 { get; set; } 
    public int Int2 { get; set; } 
} 

Czy C# zapewniają szybki łatwy sposób dodawania bezpieczeństwa wątku bez konieczności to zrobić?

public class SocksEntry 
{ 
    protected object _lock = new object(); 

    // internal 
    private int _int1 = 0; 
    private int _int2 = 0; 

    // accessors 
    protected int Int1 
    { 
     get { lock (_lock) return _int1; } 
     set { lock (_lock) _int1 = value; } 
    } 

    protected int Int2 
    { 
     get { lock (_lock) return _int2; } 
     set { lock (_lock) _int2 = value; } 
    } 
} 

To oczywiście sprawia, że ​​cała klasa jest o wiele większa i trudniejsza do stworzenia w porównaniu z wersją bez wątków!

+0

Może chcesz rzucić okiem na to: http://www.codeproject.com/Articles/33559/Handy-wrapper-class-for-thread-safe-property-acces –

+1

Twój kod z wbudowanymi zamkami w obiekcie akcesory nie są tak naprawdę bezpieczne dla wątków. Jedyne, co mogą ci kupić, to bariery pamięci, jeśli metody są wbudowane, ale operacje, które "blokujesz", są w rzeczywistości już operacjami atomowymi. Miałoby sens tylko blokowanie operacji na wyższym poziomie, które wymagają koordynacji wielu stanów. –

Odpowiedz

7

Zabezpieczenie nici do zajęć jest o wiele trudniejsze, niż można sobie wyobrazić. Może to nie wystarczyć, aby umieścić blokady wokół getterów i seterów. Najlepszą praktyką jest więc, aby klasa nie była bezpieczna i pozostawić odpowiedzialność konsumentowi tej klasy, aby zsynchronizować dostęp do niej, jeśli potrzebuje bezpieczeństwa wątku. Jeśli konsument nie potrzebuje bezpieczeństwa gwintu, to świetnie, nie będziesz karał wydajności aplikacji tylko dlatego, że wbudowałeś bezpieczeństwo gwintów w klasę, nawet nie wymagając tego. Dlaczego według Ciebie 99,99% klas w .NET nie jest bezpiecznych dla wątków?

+0

Czy możesz to zademonstrować, czy nie? – IamStalker

+2

Jasne, mogę. Wszystko, co musisz zrobić, to pozbyć się zamków we wspomnianym przykładzie. Spójrz na pierwszy fragment kodu, który zawiera OP. Tak powinna wyglądać klasa w większości przypadków. Jeśli chodzi o spożywanie tej klasy w sposób bezpieczny dla wątków, cóż, to całkowicie zależy od kontekstu. OP nie określił w rzeczywistości, w jaki sposób jest używana ta klasa, więc bez tego trudno jest podać konkretne przykłady. –

+0

Oh, tęsknię, zrozumiałem, że chcę zobaczyć wątek bezpieczny, rozwiązanie. – IamStalker

1

Jednym słowem, nie.

Własności zaimplementowane automatycznie są świetne, ale kiedy trafią się ograniczenia, to wszystko.

Można jednak utworzyć model Visual Studio snippet (np. prop), który będzie dla ciebie pisać kod płyty kotła.

0

Myślę, że problem bezpieczeństwa gwint masz na myśli coś takiego:

public int Value { get; set; } 

public void Foo() 
{ 
    if (this.Value > 0) // Read the property and make a decision based on its present value 
    { 
     // Do something with the property, assuming its value has passed your test. 
     // Note that the property's value may have been changed by another thread between the previous line and this one. 
     Console.WriteLine(this.Value); 
    } 
} 

Problemem jest to, że wartość nieruchomości może się zmieniło między kiedy zbadał go i kiedy go używać. Dzieje się tak niezależnie od tego, czy używasz właściwości automatycznych, zmiennych zapasowych, czy blokad, które masz w swoim pytaniu. Prawidłowe rozwiązanie zależy od tego, jak powinna wyglądać aplikacja (co należy zrobić, jeśli wartość zmieniła się między wierszami). Typowym rozwiązaniem jest buforować wartość prawo w funkcji gdzie używasz go:

public int Value { get; set; } 

public void Foo() 
{ 
    int cachedValue = this.Value; 

    if (cachedValue > 0) 
    { 
     Console.WriteLine(cachedValue); 
    } 
} 

jednak, że niekoniecznie będzie poprawna dla co aplikacja ma robić.

0

Najłatwiejszym i najpewniejszym jedynym właściwym podejściem jest stworzenie klasy immutable, więc dostępne są tylko operacje odczytu.

public sealed class SocksEntry 
{ 
    public int Int1 { get; private set; } 
    public int Int2 { get; private set; } 
} 

Ale jeśli nie jest to możliwe dla danego podmiotu gospodarczego - narazić kilka metod biznesowych zamiast ustawiające własności, to jest o wiele łatwiej zsynchronizować całą metodę (myślę transakcję) zamiast każdej podstawce nieruchomości, która czasami ma sens ustawić alltogether

// lock entire business transaction if possible 
public void UpdateEntity(int a, int b) 
{ 
    lock (entityLock) 
    { 
     Int1 = a; 
     Int2 = b; 
    } 
}  
+1

Uczynienie klasy niezmienną nie czyni jej bezpieczną dla wątków w ogólnym przypadku. W szczególnym przypadku z 2 właściwościami całkowitymi prawdopodobnie tak jest, ale wyobraź sobie na przykład właściwość typu 'List '. Teraz nawet jeśli zrobiłeś to tylko do odczytu, nic nie stoi na przeszkodzie, aby konsument tej klasy wywoływał gettera na liście, a następnie z jednego wątku dodając elementy do listy i od innego wątku powtarzającego się na liście w tym samym czasie. Jak wiesz "List ' nie jest typem wątku bezpiecznego i masz kłopoty, jeśli nie synchronizujesz dostępu do tej listy z zewnątrz. –

+0

Myślę, że twój przykład z 'List ' nie jest przykładem niezmiennej klasy, ponieważ jak powiedziałeś, wciąż możesz aktualizować listę – sll

+0

Tak, ale to prawda dla każdego typu odniesienia. Po uzyskaniu odwołania do typu można go zmodyfikować. Twój przykład z niezmiennymi typami dotyczy tylko typów wartości. Jak radzisz sobie z typami referencyjnymi w tym przypadku? –

0

w modelu pamięci .NET, czyta i pisze o rodzimych typów całkowitą mniejszą lub równą wielkości wskaźnika danej platformy (tj int na 32-bitowym long na 64-bit) są atomowy . Atomowy oznacza, że ​​możesz go czytać i zapisywać bez użycia blokad, a nigdy nie będziesz czytać ani pisać niekompletnych (czyli w połowie między wartościami zapisu) wartości.

Jednak.NET nie gwarantuje, że zapisy będą natychmiast widoczne dla innych wątków (co oznacza, że ​​możesz pisać w wątku A, potem czytać w wątku B i nadal czytać starą wartość). Na architekturze x86 uzyskujesz natychmiastową widoczność w architekturze, ale na innych platformach musisz użyć pola volatile po zapisie. lock wstawia dla Ciebie barierę pamięci, więc nie musisz się tym martwić.

Kolejną rzeczą, o której trzeba pomyśleć, jest to, że używasz do korzystania z klasy. Podczas gdy pojedynczy dostęp jest atomowy, lock będzie nadal potrzebny, jeśli chcesz odczytać lub zapisać wiele pól jako jedną operację atomową. (no, nie zawsze technicznie - istnieją pewne optymalizacje, które możesz czasami zrobić, ale trzymaj się prostych rzeczy podczas nauki)

Krótko mówiąc: lock jest dość przesada - możesz się pozbyć używaj lotnych/barier, jeśli potrzebujesz natychmiastowej widoczności.

Powiązane problemy