Sobes.tech
Middle+

Cum se gestionează lucrul cu firele și sarcinile în C# atunci când mai multe fire accesează o stare comună?

sobes.tech AI

Răspuns de la AI

Atunci când lucrați cu o stare comună între mai multe fire în C#, pot apărea probleme de sincronizare și condiții de cursă. Pentru a asigura siguranța firelor, se utilizează următoarele mecanisme:

  1. Blocare (Locking):

    • Operatorul lock este utilizat pentru a crea o secțiune critică care garantează că doar un fir poate accesa resursa protejată o dată.
    • Necesită un obiect de blocare (de obicei private readonly object) utilizat pentru sincronizare.
    // Exemplu de utilizare a lock
    private readonly object _lockObject = new object();
    private int _sharedCounter = 0;
    
    public void IncrementCounter()
    {
        lock (_lockObject)
        {
            _sharedCounter++; // Secțiune critică
        }
    }
    
  2. Operații atomice:

    • Clasa System.Threading.Interlocked oferă operații atomice (de exemplu, Increment, Decrement, Add, Exchange) care sunt sigure pentru fire și nu necesită blocare explicită pentru a efectua o operație.
    // Exemplu de utilizare a Interlocked
    private volatile int _sharedValue = 0; // volatile pentru vizibilitatea modificărilor
    
    public void UpdateValue(int newValue)
    {
        Interlocked.Exchange(ref _sharedValue, newValue); // Atribuire atomică
    }
    
    public void IncrementValue()
    {
        Interlocked.Increment(ref _sharedValue); // Creștere atomică
    }
    
    • Deși volatile asigură vizibilitatea modificărilor între fire, nu garantează atomicitatea operațiunilor multi-pas. Pentru operațiuni atomice, trebuie utilizat Interlocked.
  3. Clase de sincronizare din System.Threading și System.Threading.Tasks:

    • Monitor: mecanism de blocare de nivel inferior comparativ cu lock.
    • Mutex: permite sincronizarea accesului la o resursă între mai multe procese.
    • SemaphoreSlim: limitează numărul de fire care pot accesa simultan o resursă.
    • ReaderWriterLockSlim: permite mai multor fire să citească resursa în același timp, dar doar unuia să scrie.
  4. Colecții sigure pentru fire:

    • Namespace-ul System.Collections.Concurrent conține colecții (ConcurrentBag<T>, ConcurrentDictionary<TKey, TValue>, ConcurrentQueue<T>, ConcurrentStack<T>) proiectate special pentru acces concurent. Utilizarea acestor colecții elimină necesitatea sincronizării manuale pentru operațiile de bază cu colecția.
    // Exemplu de utilizare a 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. Obiecte imutabile:

    • Crearea obiectelor imutabile înseamnă că, odată create, starea lor nu poate fi schimbată. Dacă starea comună este reprezentată de un obiect imutabil, mai mulți fire îl pot citi în siguranță fără a necesita sincronizare. Actualizarea stării se reduce la crearea unei noi instanțe a obiectului.

Atunci când lucrați cu sarcini (Task) în TPL (Task Parallel Library), trebuie, de asemenea, să luați în considerare sincronizarea la accesarea stării comune. TPL simplifică crearea și gestionarea operațiilor paralele, dar nu elimină responsabilitatea de a asigura siguranța firelor atunci când lucrați cu date partajate.

Alegerea mecanismului de sincronizare potrivit depinde de sarcina specifică și de caracteristicile accesului la starea comună.