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

Что такое interlocked?

2.0 Middle🔥 102 комментариев
#Асинхронность и многопоточность#Основы C# и .NET

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

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

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

Что такое Interlocked?

Interlocked — это статический класс в пространстве имен System.Threading, предоставляющий атомарные операции для переменных, которые разделяются между несколькими потоками. Основная цель Interlocked — обеспечить потокобезопасность при выполнении простых операций над общими данными (таких как инкремент, декремент, обмен значениями) без использования более тяжеловесных механизмов синхронизации, таких как lock, Monitor или Mutex.

Для чего нужен Interlocked?

В многопоточных приложениях, когда несколько потоков обращаются к одной переменной одновременно, могут возникать состояния гонки (race conditions), приводящие к неопределенному поведению и ошибкам. Например, операция i++ (инкремент) на уровне процессора состоит из трёх шагов: чтение значения, увеличение, запись. Если два потока выполняют эти шаги параллельно, результат может быть некорректным. Interlocked решает эту проблему, выполняя такие операции атомарно — как единое целое, которое нельзя прервать.

Ключевые методы класса Interlocked

Класс предоставляет множество статических методов. Основные из них:

  • Increment/Decrement: Атомарно увеличивает или уменьшает значение переменной.

    int counter = 0;
    Interlocked.Increment(ref counter); // counter = 1
    Interlocked.Decrement(ref counter); // counter = 0
    
  • Add: Атомарно добавляет значение к переменной.

    int value = 5;
    Interlocked.Add(ref value, 3); // value = 8
    
  • Exchange: Атомарно задаёт новое значение переменной и возвращает её старое значение.

    int sharedValue = 10;
    int oldValue = Interlocked.Exchange(ref sharedValue, 20); // sharedValue = 20, oldValue = 10
    
  • CompareExchange: Атомарно сравнивает переменную с ожидаемым значением и, если они равны, задаёт новое значение. Возвращает исходное значение переменной.

    int target = 1;
    int expected = 1;
    int newValue = 2;
    int result = Interlocked.CompareExchange(ref target, newValue, expected); 
    // Если target был равен expected (1), то target станет 2. result будет исходным target (1).
    
  • Read: Атомарно считывает 64-битное значение (long, ulong). Это важно на 32-битных системах, где чтение 64-битных значений не является атомарным по умолчанию.

    long bigCounter = 100;
    long value = Interlocked.Read(ref bigCounter); // Атомарное чтение
    
  • MemoryBarrier: Устанавливает барьер памяти, который предотвращает переупорядочивание операций чтения/записи процессором или компилятором.

Пример использования

Предположим, у нас есть счётчик, который увеличивается из нескольких потоков. Без синхронизации результат может быть некорректным. С Interlocked мы гарантируем правильность:

using System.Threading;

class Program
{
    private static int counter = 0;

    static void Main()
    {
        Thread thread1 = new Thread(IncrementCounter);
        Thread thread2 = new Thread(IncrementCounter);

        thread1.Start();
        thread2.Start();

        thread1.Join();
        thread2.Join();

        Console.WriteLine($"Итоговое значение counter: {counter}"); // Всегда 20000
    }

    static void IncrementCounter()
    {
        for (int i = 0; i < 10000; i++)
        {
            Interlocked.Increment(ref counter);
        }
    }
}

Преимущества и недостатки

Преимущества:

  • Высокая производительность: Методы Interlocked обычно реализуются на уровне процессора (через инструкции типа LOCK XADD в x86/x64) и работают значительно быстрее, чем использование lock.
  • Отсутствие блокировок (lock-free): Операции атомарны и не требуют блокировки потоков, что снижает риски взаимоблокировок (deadlocks) и повышает масштабируемость.
  • Простота использования: Для простых операций (инкремент, обмен) не нужно писать сложный код синхронизации.

Недостатки:

  • Ограниченный набор операций: Подходит только для базовых действий над примитивными типами (int, long, float, double, указатели). Для сложных структур данных или составных операций требуется использовать другие механизмы (например, lock).
  • Не подходит для всех сценариев: Если нужно выполнить несколько операций как одну атомарную (например, проверить условие и изменить несколько переменных), Interlocked не поможет — нужен lock или Monitor.

Внутренняя реализация

Методы Interlocked используют низкоуровневые процессорные инструкции, которые гарантируют атомарность на уровне железа. Например, на платформе x86/x64 для Increment может использоваться инструкция LOCK XADD. Эти инструкции обеспечивают, что операция будет завершена целиком до того, как другой процессор или ядро получит доступ к той же памяти.

Когда использовать Interlocked?

  • Счётчики и статистика: Например, подсчёт количества обработанных запросов в многопоточном сервере.
  • Флаги и состояния: Атомарное переключение состояния (например, флаг "выполняется").
  • Lock-free алгоритмы: При реализации неблокирующих структур данных (стеки, очереди) часто используются CompareExchange и Exchange.
  • Обновление shared переменных: Когда несколько потоков обновляют общую переменную простым образом.

Сравнение с lock

// С использованием Interlocked
Interlocked.Increment(ref counter);

// С использованием lock
object lockObj = new object();
lock (lockObj)
{
    counter++;
}

Первый вариант быстрее и не блокирует потоки, но второй более гибкий и подходит для сложных операций.

Заключение

Interlocked — это мощный инструмент для обеспечения потокобезопасности в высокопроизводительных многопоточных приложениях на C#. Он предоставляет атомарные операции, которые выполняются без блокировок, используя низкоуровневые механизмы процессора. Хотя его применение ограничено простыми операциями, в подходящих сценариях он даёт значительный прирост производительности по сравнению с традиционными блокировками. Важно понимать его принципы, чтобы правильно выбирать между Interlocked, lock и другими средствами синхронизации в зависимости от задачи.