Ответ
Потокобезопасность — это свойство кода (класса, метода, структуры данных) корректно работать в многопоточной среде, когда несколько потоков обращаются к нему одновременно, без возникновения ошибок данных или неопределённого поведения.
Основные угрозы, от которых защищает потокобезопасность:
- Состояние гонки (Race Condition): Результат выполнения зависит от неуправляемого порядка выполнения потоков.
- Некорректные видимые изменения (Visibility): Изменения, сделанные одним потоком в общей памяти, могут быть не сразу (или никогда) видны другому потоку из-за кэширования процессором.
- Взаимная блокировка (Deadlock): Два или более потока бесконечно ждут друг друга, освобождая необходимые им ресурсы.
Механизмы обеспечения потокобезопасности в C#:
-
lock(критическая секция): Самый простой способ для защиты блока кода.private readonly object _syncLock = new object(); private int _sharedCounter = 0; public void IncrementSafe() { lock (_syncLock) // Только один поток может выполнять этот блок в данный момент. { _sharedCounter++; // Операция теперь атомарна и безопасна. } } -
Атомарные операции с
Interlocked: Для простых операций над примитивами (инкремент, сравнение с обменом). Гораздо быстрееlock.Interlocked.Increment(ref _sharedCounter); -
Специализированные примитивы:
Monitor,Mutex(для межпроцессного взаимодействия),Semaphore,ReaderWriterLockSlim(оптимизирован для сценариев «много чтений, редкая запись»). -
Потокобезопасные коллекции (
ConcurrentQueue,ConcurrentDictionary): Встроенные реализации, избавляющие от необходимости ручной синхронизации. -
Неизменяемость (Immutability): Самый надёжный способ. Если объект после создания не может быть изменён, его можно свободно передавать между потоками.
public record ImmutableData(int Id, string Name); // Record в C# 9+ по умолчанию неизменяем.
Пример опасного (непотокобезопасного) кода:
int counter = 0;
Parallel.For(0, 10000, i =>
{
counter++; // Операция не атомарна: чтение -> увеличение -> запись.
});
Console.WriteLine(counter); // Результат будет меньше 10000 из-за потерь при гонке.
Важно: Потокобезопасность имеет стоимость (блокировки замедляют код). Не стоит делать потокобезопасным всё подряд. Применяйте синхронизацию только к действительно общим, изменяемым данным, и выбирайте наиболее подходящий и менее затратный механизм для конкретной задачи.