← Назад к вопросам

Как управлять синхронизацией потоков?

2.0 Middle🔥 111 комментариев
#Асинхронность и многопоточность

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Управление синхронизацией потоков в C#

Управление синхронизацией потоков — критически важная задача в многопоточном программировании на C#, позволяющая избежать гонок данных, состояний гонки и обеспечить потокобезопасность. В .NET предлагается богатый набор примитивов синхронизации, которые можно разделить на несколько категорий.

Основные примитивы синхронизации

1. Блокировки (Locks)

Наиболее распространенный механизм для обеспечения эксклюзивного доступа.

// 1. Оператор lock (монитор)
private readonly object _lockObj = new object();
public void ThreadSafeMethod()
{
    lock (_lockObj)
    {
        // Критическая секция
        sharedResource++;
    }
}

// 2. Мьютекс (межпроцессный)
using var mutex = new Mutex(false, "GlobalMutexName");
mutex.WaitOne();
try
{
    // Работа с общим ресурсом
}
finally
{
    mutex.ReleaseMutex();
}

2. Читатели-

Для сценариев "много читателей, один писатель":

private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
public void ReadOperation()
{
    _rwLock.EnterReadLock();
    try
    {
        // Множество потоков могут читать одновременно
    }
    finally
    {
        _rwLock.ExitReadLock();
    }
}
public void WriteOperation()
{
    _rwLock.EnterWriteLock();
    try
    {
        // Только один поток может писать
    }
    finally
    {
        _rwLock.ExitWriteLock();
    }
}

3. Семафоры

Ограничивают количество потоков, одновременно работающих с ресурсом:

// Семафор с максимальным количеством 5 параллельных потоков
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(5);
public async Task LimitedAccessMethod()
{
    await _semaphore.WaitAsync();
    try
    {
        // Не более 5 потоков одновременно
    }
    finally
    {
        _semaphore.Release();
    }
}

4. Межпоточные сигналы

// ManualResetEvent и AutoResetEvent
private readonly ManualResetEventSlim _mre = new ManualResetEventSlim(false);
public void SignalWorker()
{
    _mre.Set(); // Сигнал всем ожидающим потокам
}
public void WaitForSignal()
{
    _mre.Wait(); // Блокировка до получения сигнала
}

// CountdownEvent - ожидание нескольких операций
var countdown = new CountdownEvent(3);
Parallel.For(0, offshore, i => 
{
    // Выполнение работы
    countdown.Signal();
});
countdown.Wait(); // Ожидание завершения 3 операций

Атомарные операции

Для простых операций с примитивными типами используйте Interlocked класс:

private int _counter = 0;
public void IncrementSafely()
{
    // Атомарное инкрементирование
    Interlocked.Increment(ref _counter);
    
    // Атомарное сравнение и замена (CAS)
    int original;
    do
    {
        original = _counter;
    } while (Interlocked.CompareExchange(ref _counter, original + 10, original) != original);
}

Асинхронная синхронизация

В async/await коде предпочтительны асинхронные примитивы:

private readonly SemaphoreSlim _asyncLock = new SemaphoreSlim(1, 1);
public async Task AsyncThreadSafeMethod()
{
    await _asyncLock.WaitAsync();
    try
    {
        await SomeAsyncOperation();
    }
    finally
    {
        _asyncLock.Release();
    }
}

Рекомендации по использованию

  1. Минимизируйте время блокировки — держите lock только на время критической секции
  2. Избегайте вложенных блокировок — это основной источник взаимных блокировок (deadlocks)
  3. Используйте timeout для предотвращения вечной блокировки:
    if (Monitor.TryEnter(_lockObj, TimeSpan.FromSeconds(5)))
    {
        try { /* работа */ }
        finally { Monitor.Exit(_lockObj); }
    }
    
  4. Отдавайте предпочтение ReaderWriterLockSlim для read-heavy workloads
  5. Для async кода используйте SemaphoreSlim вместо классических примитивов
  6. Рассматривайте lock-free структуры (ConcurrentBag, ConcurrentDictionary) где возможно

Продвинутые техники

Для сложных сценариев рассматривайте:

  • SpinWait для кратковременного активного ожидания
  • Barrier для синхронизации нескольких потоков в фазах
  • Channels из System.Threading.Channels для producer-consumer паттернов
  • Иммутабельные структуры данных как альтернативу синхронизации

Ключевой принцип: выбирайте самый простой и легковесный примитив, соответствующий вашим требованиям. Monitor/lock достаточно для большинства сценариров эксклюзивного доступа, в то время как ReaderWriterLockSlim оптимизирован для смешанных read-write workloads. Всегда тестируйте многопоточный код под нагрузкой для выявления скрытых проблем синхронизации.