Sobes.tech
Middle+

Как се работи с нишки и задачи в C#, когато няколко нишки достъпват общо състояние?

sobes.tech AI

Отговор от AI

Когато работите с общо състояние в C# с няколко нишки, възникват проблеми със синхронизация и възможни състезателни условия. Използват се следните механизми:

  1. Блокиране (Locking):

    • Използва се операторът lock за създаване на критична секция, която гарантира, че само една нишка може да получи достъп до защитения ресурс наведнъж.
    • Изисква се обект-заглушка (обикновено private readonly object), който се използва за синхронизация.
    // Пример за използване на lock
    private readonly object _lockObject = new object();
    private int _sharedCounter = 0;
    
    public void IncrementCounter()
    {
        lock (_lockObject)
        {
            _sharedCounter++; // Критична секция
        }
    }
    
  2. Атомарни операции:

    • Класът System.Threading.Interlocked предоставя атомарни операции (например, Increment, Decrement, Add, Exchange), които са потокобезопасни и не изискват явна блокировка.
    // Пример за използване на Interlocked
    private volatile int _sharedValue = 0; // volatile за видимост на промените
    
    public void UpdateValue(int newValue)
    {
        Interlocked.Exchange(ref _sharedValue, newValue); // Атомарно присвояване
    }
    
    public void IncrementValue()
    {
        Interlocked.Increment(ref _sharedValue); // Атомарно увеличение
    }
    
    • Въпреки че volatile осигурява видимост на промените между нишки, той не гарантира атомарността на многостъпковите операции. За атомарни операции трябва да използвате Interlocked.
  3. Класове за синхронизация от System.Threading и System.Threading.Tasks:

    • Monitor: по-нискоуровнев механизъм за блокиране в сравнение с lock.
    • Mutex: позволява синхронизиране на достъпа до ресурс между няколко процеса.
    • SemaphoreSlim: ограничава броя на нишките, които могат да получат достъп до ресурса едновременно.
    • ReaderWriterLockSlim: позволява на множество нишки да четат ресурса едновременно, но само на една да пише.
  4. Потокобезопасни колекции:

    • Пространството от имена System.Collections.Concurrent съдържа колекции (ConcurrentBag<T>, ConcurrentDictionary<TKey, TValue>, ConcurrentQueue<T>, ConcurrentStack<T>), проектирани специално за многопоточен достъп. Използването на тези колекции премахва необходимостта от ръчно синхронизиране при основните операции с колекцията.
    // Пример за използване на 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. Неменяеми обекти (Immutable objects):

    • Създаването на неменяеми обекти означава, че тяхното състояние не може да бъде променено след създаването им. Ако общото състояние е представено от неменяем обект, множество нишки могат безопасно да го четат без необходимост от синхронизация. Обновяването на състоянието се свежда до създаване на нов екземпляр.

При работа с задачи (Task) в TPL (Task Parallel Library) също трябва да се вземе предвид синхронизацията при достъпа до общото състояние. TPL улеснява създаването и управлението на паралелни операции, но не премахва отговорността за осигуряване на потокобезопасност при работа с общи данни.

Изборът на подходящ механизъм за синхронизация зависи от конкретната задача и характеристиките на достъпа до общото състояние.