В ноябре 2024 года с выходом .NET 9 и обновлением C# до версии 13 мы получили интересные нововведения, которые касаются типов данных и семантики блокировок. Каждый крупный релиз .NET сопровождается новыми инструментами, которые улучшают производительность, безопасность и удобство разработки. В C# 13 такой новинкой стал System.Threading.Lock. Это попытка сделать многопоточность чуть менее токсичной и чуть более предсказуемой.

Что не так со старым lock?

Традиционно в C# для обеспечения взаимного исключения потоков используется ключевое слово lock в сочетании с любым объектом (object). Однако такой подход может приводить к узким местам в производительности и потенциальным рискам взаимной блокировки (deadlock).

С выходом C# 13 и .NET 9 был представлен новый тип блокировки — System.Threading.Lock. Это специализированный механизм синхронизации, который улучшает управление многопоточностью, предоставляя более безопасный и производительный способ блокировки потоков.

Почему это важно?

Многопоточность — это всегда компромисс между производительностью и сложностью реализации. System.Threading.Lock предлагает два основных преимущества:

  1. Исключение ошибок при выборе объекта для блокировки: использование специального типа блокировки вместо произвольных объектов уменьшает вероятность ошибок и улучшает контроль за синхронизацией.

  2. Гибкость и контроль: с System.Threading.Lock вы можете настроить тайм-ауты и отмену операций, избегая зависания потоков.

Кроме того, новая структура эффективнее работает в сценариях с высокой нагрузкой.

Основные сценарии применения

  • Приложения с высокими требованиями к производительности: в средах с высокой конкуренцией, где требуется частая блокировка и разблокировка, System.Threading.Lock снижает издержки на переключение контекста.

  • Сложные задачи синхронизации: для приложений, требующих тонкой настройки блокировок, новый тип блокировки предоставляет более гибкий API.

  • Избежание взаимоблокировок: новые возможности для управления блокировками, такие как тайм-ауты и отмена, значительно снижают вероятность возникновения взаимоблокировок.

Пример:

Ниже приведен System.Threading.Lockпример кода, демонстрирующий безопасное обновление общего ресурса в многопоточной среде:

using System;
using System.Threading;
using System.Threading.Tasks;

public class Account
{
    private decimal _balance;
    private Lock _balanceLock = new Lock();

    public Account(decimal initialBalance)
    {
        _balance = initialBalance;
    }

    public void Debit(decimal amount)
    {
        if (amount <= 0)
            throw new ArgumentException("Amount must be positive", nameof(amount));

        using (_balanceLock.EnterScope())
        {
            if (_balance < amount)
                throw new InvalidOperationException("Insufficient funds");

            _balance -= amount;
        }
    }

    public void Credit(decimal amount)
    {
        if (amount <= 0)
            throw new ArgumentException("Amount must be positive", nameof(amount));

        using (_balanceLock.EnterScope())
        {
            _balance += amount;
        }
    }

    public decimal GetBalance()
    {
        using (_balanceLock.EnterScope())
        {
            return _balance;
        }
    }
}

public class Program
{
    public static async Task Main()
    {
        var account = new Account(1000m);

        var tasks = new Task[10];
        for (int i = 0; i < tasks.Length; i++)
        {
            tasks[i] = Task.Run(() =>
            {
                for (int j = 0; j < 100; j++)
                {
                    account.Credit(10);
                    account.Debit(10);
                }
            });
        }

        await Task.WhenAll(tasks);

        Console.WriteLine($"Final balance: {account.GetBalance()}");
    }
}

В приведенном коде:

  • Класс Account: представляет банковский счёт, включает методы для списания, зачисления и получения баланса.

  • Поле _balanceLock: использует новый тип Lock из System.Threading, чтобы обеспечить потокобезопасный доступ к полю _balance и избежать ошибок, связанных с выбором объекта для традиционного lock.

  • Метод EnterScope(): применяется для входа в область блокировки, обеспечивая, что доступ к общему ресурсу будет безопасным и взаимно-исключающим, благодаря специализированному типу блокировки.

  • Оператор using: автоматизирует освобождение блокировки в конце области действия, предотвращая возникновение взаимоблокировок и упрощая управление блокировками.

Благодаря использованию нового типа System.Threading.Lock код обеспечивает более эффективную и гибкую синхронизацию потоков, снижая риски, связанные с производительностью и потенциальными проблемами, возникающими при использовании универсальных объектов для lock.

Реализация System.Threading.Lock основана на следующих ключевых концепциях:

  • Специализированный объект блокировки: System.Threading.Lock — это тип, специально разработанный для синхронизации потоков, что позволяет избежать недостатков традиционного подхода с использованием произвольных объектов для блокировок.

  • Управление областью действия: метод EnterScope() обеспечивает вход в область блокировки, гарантируя потокобезопасный доступ к общим ресурсам.

  • Автоматическое освобождение: использование оператора using помогает автоматически освободить блокировку, предотвращая потенциальные взаимоблокировки и обеспечивая более чистое и безопасное управление синхронизацией.

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


  1. cstrike
    24.01.2025 12:35

    Где примеры использования? Где side-by-side сравнение кода? Простите, но статья пустая. Что-то там восхваляется, но эти фанфары ничем не подтверждены.


  1. TerekhinSergey
    24.01.2025 12:35

    А ещё бы сравнение с другими примитивами - семафором, readwrite lock, которые уже есть... Бенчмарки для всех вариантов бы


  1. withkittens
    24.01.2025 12:35

    Мне кажется, вы неправильно поняли фичу? lock никуда не девается, плюшки появляются из-за использования System.Threading.Lock вместо object, а не потому что мы lock вручную переписываем на using, в доке есть пример (подозрительно похожий на ваш ;)


  1. ryanl
    24.01.2025 12:35

    "в средах с высокой конкуренцией, где требуется частая блокировка и разблокировка, System.Threading.Lock снижает издержки на переключение контекста." - Ла ла ла, дай-ка я на хабр напишу ерунды в пятницу...


  1. Ydav359
    24.01.2025 12:35

    Статья от нейросети?