Sobes.tech
Middle+

Ako sa v C# pracuje s vláknami a úlohami, keď viaceré vlákna pristupujú k spoločnému stavu?

sobes.tech AI

Odpoveď od AI

Pri práci so spoločným stavom v C# s viacerými vláknami vznikajú problémy so synchronizáciou a možné závodné podmienky. Používajú sa nasledujúce mechanizmy:

  1. Zamykanie (Locking):

    • Používa sa operátor lock na vytvorenie kritickej sekcie, ktorá zabezpečuje, že iba jedno vlákno môže získať prístup ku chránenému zdroju naraz.
    • Vyžaduje sa objekt-zásterka (zvyčajne private readonly object) používaný na synchronizáciu.
    // Príklad použitia lock
    private readonly object _lockObject = new object();
    private int _sharedCounter = 0;
    
    public void IncrementCounter()
    {
        lock (_lockObject)
        {
            _sharedCounter++; // Kritická sekcia
        }
    }
    
  2. Atómové operácie:

    • Trieda System.Threading.Interlocked poskytuje atómové operácie (napríklad Increment, Decrement, Add, Exchange), ktoré sú vlákno-bezpečné a nevyžadujú explicitné zamykanie.
    // Príklad použitia Interlocked
    private volatile int _sharedValue = 0; // volatile pre viditeľnosť zmien
    
    public void UpdateValue(int newValue)
    {
        Interlocked.Exchange(ref _sharedValue, newValue); // Atómové priradenie
    }
    
    public void IncrementValue()
    {
        Interlocked.Increment(ref _sharedValue); // Atómické zvýšenie
    }
    
    • Hoci volatile zabezpečuje viditeľnosť zmien medzi vláknami, nezaručuje atómovosť viacstupňových operácií. Pre atómové operácie je potrebné používať Interlocked.
  3. Triedy na synchronizáciu z System.Threading a System.Threading.Tasks:

    • Monitor: nižšia úroveň mechanizmu zamykania v porovnaní s lock.
    • Mutex: umožňuje synchronizovať prístup k zdroju medzi viacerými procesmi.
    • SemaphoreSlim: obmedzuje počet vlákien, ktoré môžu súčasne získať prístup ku zdroju.
    • ReaderWriterLockSlim: umožňuje viacerým vláknam čítať zdroj súčasne, ale len jednému zapisovať.
  4. Vlákno-bezpečné kolekcie:

    • Priestor názvov System.Collections.Concurrent obsahuje kolekcie (ConcurrentBag<T>, ConcurrentDictionary<TKey, TValue>, ConcurrentQueue<T>, ConcurrentStack<T>), navrhnuté špeciálne pre viacvláknový prístup. Použitie týchto kolekcií odstraňuje potrebu manuálnej synchronizácie pri základných operáciách s kolekciou.
    // Príklad použitia 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. Nemenné objekty (Immutable objects):

    • Vytváranie nemenných objektov znamená, že ich stav nemôže byť zmenený po ich vytvorení. Ak je spoločný stav reprezentovaný nemenným objektom, viaceré vlákna ho môžu bezpečne čítať bez potreby synchronizácie. Aktualizácia stavu spočíva vo vytvorení nového inštancie objektu.

Pri práci s úlohami (Task) v TPL (Task Parallel Library) je tiež potrebné zvážiť synchronizáciu pri prístupe ku spoločnému stavu. TPL uľahčuje vytváranie a správu paralelných operácií, ale neodstraňuje zodpovednosť za zabezpečenie vlákno-bezpečnosti pri práci so spoločnými dátami.

Výber vhodného mechanizmu synchronizácie závisí od konkrétnej úlohy a charakteristík prístupu ku spoločnému stavu.