Sobes.tech
Middle+

Jak se v C# pracuje s vlákny a úkoly, když několik vláken přistupuje ke společnému stavu?

sobes.tech AI

Odpověď od AI

Při práci se sdíleným stavem v C# s více vlákny vznikají problémy se synchronizací a možné závodní podmínky. Používají se následující mechanismy:

  1. Zamykání (Locking):

    • Používá se operátor lock k vytvoření kritické sekce, která zaručuje, že pouze jedno vlákno může najednou přistupovat k chráněnému zdroji.
    • Vyžaduje objekt-uzávěr (obvykle private readonly object) používaný ke synchronizaci.
    // Příklad použití lock
    private readonly object _lockObject = new object();
    private int _sharedCounter = 0;
    
    public void IncrementCounter()
    {
        lock (_lockObject)
        {
            _sharedCounter++; // Kritická sekce
        }
    }
    
  2. Atomické operace:

    • Třída System.Threading.Interlocked poskytuje atomické operace (například Increment, Decrement, Add, Exchange), které jsou vlákny bezpečné a nevyžadují explicitní zamykání.
    // Příklad použití Interlocked
    private volatile int _sharedValue = 0; // volatile pro viditelnost změn
    
    public void UpdateValue(int newValue)
    {
        Interlocked.Exchange(ref _sharedValue, newValue); // Atomické přiřazení
    }
    
    public void IncrementValue()
    {
        Interlocked.Increment(ref _sharedValue); // Atomické zvýšení
    }
    
    • Ačkoliv volatile zajišťuje viditelnost změn mezi vlákny, nezaručuje atomickost vícestupňových operací. Pro atomické operace je třeba používat Interlocked.
  3. Třídy synchronizace z System.Threading a System.Threading.Tasks:

    • Monitor: nižší úroveň mechanismu zamykání ve srovnání s lock.
    • Mutex: umožňuje synchronizovat přístup ke zdroji mezi více procesy.
    • SemaphoreSlim: omezuje počet vláken, která mohou současně získat přístup ke zdroji.
    • ReaderWriterLockSlim: umožňuje více vláknům číst zdroj současně, ale pouze jednomu zapisovat.
  4. Vlákny bezpečné kolekce:

    • Prostor názvů System.Collections.Concurrent obsahuje kolekce (ConcurrentBag<T>, ConcurrentDictionary<TKey, TValue>, ConcurrentQueue<T>, ConcurrentStack<T>), navržené speciálně pro vícevláknový přístup. Použití těchto kolekcí odstraňuje potřebu ruční synchronizace při základních operacích s kolekcí.
    // Příklad použití ConcurrentDictionary
    private ConcurrentDictionary<string, int> _sharedData = new ConcurrentDictionary<string, int>();
    
    public void AddOrUpdateData(string key, int value)
    {
        _sharedData.AddOrUpdate(key, value, (k, oldValue) => oldValue + value);
    }
    
  5. Neměnné objekty (Immutable objects):

    • Vytváření neměnných objektů znamená, že jejich stav nelze změnit po jejich vytvoření. Pokud je sdílený stav reprezentován neměnným objektem, může ho více vláken bezpečně číst bez nutnosti synchronizace. Aktualizace stavu spočívá ve vytvoření nového instance objektu.

Při práci s úkoly (Task) v TPL (Task Parallel Library) je také třeba vzít v úvahu synchronizaci při přístupu ke sdílenému stavu. TPL usnadňuje vytváření a správu paralelních operací, ale nezbavuje vás odpovědnosti za zajištění bezpečnosti při práci s sdílenými daty.

Výběr vhodného mechanismu synchronizace závisí na konkrétním úkolu a charakteristikách přístupu ke sdílenému stavu.