Sobes.tech
Middle+

Kako se u C# radi sa nitima i zadacima kada više niti pristupaju zajedničkom stanju?

sobes.tech АИ

Одговор од АИ

Kada radite sa zajedničkim stanjem u C# sa više niti, javljaju se problemi sa sinhronizacijom i moguće trkačke uslove. Sledeći mehanizmi se koriste:

  1. Zaključavanje (Locking):

    • Koristi se operator lock za kreiranje kritične sekcije, koja garantuje da samo jedna nit može pristupiti zaštićenom resursu odjednom.
    • Potreban je objekat-zaštitnik (obično private readonly object) koji se koristi za sinhronizaciju.
    // Primer korišćenja lock
    private readonly object _lockObject = new object();
    private int _sharedCounter = 0;
    
    public void IncrementCounter()
    {
        lock (_lockObject)
        {
            _sharedCounter++; // Kritična sekcija
        }
    }
    
  2. Atomarne operacije:

    • Klasa System.Threading.Interlocked pruža atomarne operacije (npr., Increment, Decrement, Add, Exchange), koje su sigurnе za niti i ne zahtevaju eksplicitno zaključavanje.
    // Primer korišćenja Interlocked
    private volatile int _sharedValue = 0; // volatile za vidljivost promena
    
    public void UpdateValue(int newValue)
    {
        Interlocked.Exchange(ref _sharedValue, newValue); // Atomik dodeljivanje
    }
    
    public void IncrementValue()
    {
        Interlocked.Increment(ref _sharedValue); // Atomik povećanje
    }
    
    • Iako volatile obezbeđuje vidljivost promena između niti, ne garantuje atomarnost višestepenih operacija. Za atomarne operacije, koristi se Interlocked.
  3. Klase za sinhronizaciju iz System.Threading i System.Threading.Tasks:

    • Monitor: nižeg nivoa mehanizam zaključavanja u poređenju sa lock.
    • Mutex: omogućava sinhronizaciju pristupa resursu između više procesa.
    • SemaphoreSlim: ograničava broj niti koje istovremeno mogu pristupiti resursu.
    • ReaderWriterLockSlim: omogućava više niti da čitaju resurs istovremeno, ali samo jedna da piše.
  4. Sigurne za niti kolekcije:

    • Prostor imena System.Collections.Concurrent sadrži kolekcije (ConcurrentBag<T>, ConcurrentDictionary<TKey, TValue>, ConcurrentQueue<T>, ConcurrentStack<T>), dizajnirane posebno za višestruki pristup. Korišćenje ovih kolekcija uklanja potrebu za ručnom sinhronizacijom pri osnovnim operacijama sa kolekcijom.
    // Primer korišćenja 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. Nemijenjivi objekti (Immutable objects):

    • Kreiranje nemijenjivih objekata znači da se njihovo stanje ne može promijeniti nakon kreiranja. Ako je zajedničko stanje predstavljeno nemijenjivim objektom, više niti može sigurno da ga čita bez potrebe za sinhronizacijom. Ažuriranje stanja svodi se na kreiranje novog primjerka objekta.

Prilikom rada sa zadacima (Task) u TPL (Task Parallel Library) takođe je potrebno razmotriti sinhronizaciju pri pristupu zajedničkom stanju. TPL olakšava kreiranje i upravljanje paralelnim operacijama, ali ne uklanja odgovornost za obezbeđenje sigurnosti pri radu sa zajedničkim podacima.

Izbor odgovarajućeg mehanizma za sinhronizaciju zavisi od konkretne zadatka i karakteristika pristupa zajedničkom stanju.