2015-05-28 17 views
5

Mam następujący scenariusz:Lepsza rada zamek Wątek potrzebne

próbuję zablokować wątek na miejscu, jeśli jest to zwyczaj 'id wątki odpowiada ten, który został już wprowadzony zablokowaną sekcję kodu, ale nie, jeśli identyfikator jest różny.

stworzyłem przykładowy kod wyjaśnić zachowanie chcę

class A 
{   
    private static Dictionary<int, object> _idLocks = new Dictionary<int, object>(); 
    private static readonly object _DictionaryLock = new object(); 
    private int _id; 

    private void A (int id) 
    { 
     _id = id; 
    } 

    private object getObject() 
    { 
     lock (_DictionaryLock) 
     { 
      if (!_idLocks.ContainsKey(_id)) 
       _idLocks.Add(_id, new object()); 
     } 
     lock (_idLocks[_id]) 
     { 
      if (TestObject.Exists(_id)) 
       return TestObject(_id); 
      else 
       return CreateTestObject(_id); 
     } 
    } 
} 

Teraz to działa w 100% za to, co inne, gdzie przykładem id 1 nie sprawdzić, czy jej przedmiot został stworzony, podczas gdy inny wątek o id 1 jest już zajęty tworzeniem tego obiektu.

Ale posiadanie dwóch zamków i statycznego słownika nie wydaje się właściwym sposobem robienia tego, więc mam nadzieję, że ktoś może pokazać mi ulepszoną metodę zatrzymywania wątku przed dostępem do kodu tylko wtedy, gdy ten wątek został utworzony za pomocą ten sam identyfikator co ten, który już zajęty jest wykonywaniem kodu w zablokowanej sekcji.

Patrzyłem na klasę ReaderWriterLockSlim, ale dla mnie nie miało to sensu, ponieważ nie chcę, aby obiekt TestObject (id) był w ogóle czytany, gdy jest jeszcze tworzony.

Nie przejmuję się blokowaniem wątku przed dostępem do słownika. Za wszelką cenę próbuję uniknąć _id, w którym ten wątek działa, nie powinien być używany wewnątrz CreateTestObject(_id), podczas gdy jest już zajęty, ponieważ pliki są tworzone i usuwane z tym identyfikatorem, który będzie zgłaszał wyjątki, jeśli dwa wątki próbują aby uzyskać dostęp do tych samych plików

Które można naprawić za pomocą zwykłej blokady, ale w tym przypadku nadal chcę wątku, którego _id nie jest obecnie uruchomiony w metodzie CreateTestObject(_id), aby móc wprowadzić kod w zamku.

To wszystko dlatego, że to, co dzieje się wewnątrz obiektu CreateTestObject, wymaga czasu i wydajności, jeśli wątek oczekuje na dostęp do niego.

+0

Czy można zablokować cały słownik '_idLocks' zamiast dwóch blokad? Jestem całkiem nowy w blokowaniu, więc nie jestem pewien, co możesz użyć jako obiektu blokady, czy nie. –

Odpowiedz

3

Wygląda na to, że używasz tego kodu do zapełniania słownika w sposób bezpieczny dla wątków - czy zamiast tego można użyć wartości ConcurrentDictionary?

class A { 
    private static ConcurrentDictionary<int, object> _dictionary = new ConcurrentDictionary<int, object>(); 

    private int _id; 

    private object GetObject() { 
    object output = null; 
    if(_dictionary.TryGetValue(_id, output)) { 
     return output; 
    } else { 
     return _dictionary.GetOrAdd(_id, CreateTestObject(_id)); 
    } 
    } 
} 

Edit: Jeśli chcesz, aby całkowicie wyeliminować możliwość powołania się powielać CreateTestObject metod następnie można przechowywać opakowanie w _dictionary że leniwie ustawia object

class Wrapper { 
    private volatile object _obj = null; 

    public object GetObj() { 
    while(_obj == null) { 
     // spin, or sleep, or whatever 
    } 
    return _obj; 
    } 

    public void SetObj(object obj) { 
    _obj = obj; 
    } 
} 

class A { 
    private static ConcurrentDictionary<int, Wrapper> _dictionary = new ConcurrentDictionary<int, Wrapper>(); 

    private int _id; 

    private object GetObject() { 
    Wrapper wrapper = null; 
    if(_dictionary.TryGetValue(_id, wrapper)) { 
     return wrapper.GetObj(); 
    } else { 
     Wrapper newWrapper = new Wrapper(); 
     wrapper = _dictionary.GetOrAdd(_id, newWrapper); 
     if(wrapper == newWrapper) { 
     wrapper.SetObj(CreateTestObject(_id)); 
     } 
     return wrapper.GetObj(); 
    } 
    } 
} 

tylko jeden wątek będzie mógł wstawić nowy Wrapper w _dictionary pod podanym _id - ten wątek zainicjuje obiekt wewnątrz warunkowego wrapper == newWrapper. Wrapper#GetObj obraca się, dopóki obiekt nie zostanie ustawiony, można go przepisać na blok zamiast tego.

+0

Nie wiedziałem, że istnieje "ConcurrentDictionary". +1! –

+0

Witam, zaktualizowałem moje oryginalne pytanie, aby było bardziej zrozumiałe, dziękuję za dotychczasową pomoc – Domitius

+0

@Domitius Zaktualizowałem swoją odpowiedź –

0

To nie może działać, ponieważ Monitor (używane wewnętrznie przez oświadczenie lock) jest ponownie wprowadzane. Oznacza to, że wątek może wejść do dowolnej blokady, którą już posiada dowolną liczbę razy.

Można rozwiązać ten problem za pomocą Semaphore zamiast Monitor, ale zatrzymać się na chwilę i posłuchaj co pytasz - chcesz gwint do blokowania na lock należącego do tego samego wątku.W jaki sposób będzie się budzić ten wątek? Będzie trwać wiecznie - czekając na zwolnienie lock, jednocześnie będąc tym, który trzyma lock.

Czy po prostu próbujesz poradzić sobie z leniwą inicjalizacją jakiegoś obiektu bez konieczności blokowania wszystkich innych wątków? To całkiem proste:

ConcurrentDictionary<int, YourObject> dictionary; 

return dictionary.GetOrAdd(id, i => CreateTestObject(i)); 

Zauważ, że CreateTextObject nazywa tylko jeśli klucz nie istnieje w słowniku jeszcze.

+0

Cześć, zaktualizowałem moje oryginalne pytanie, aby było bardziej zrozumiałe, dziękuję za dotychczasową pomoc – Domitius

Powiązane problemy