В ноябре 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
предлагает два основных преимущества:
Исключение ошибок при выборе объекта для блокировки: использование специального типа блокировки вместо произвольных объектов уменьшает вероятность ошибок и улучшает контроль за синхронизацией.
Гибкость и контроль: с
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)
TerekhinSergey
24.01.2025 12:35А ещё бы сравнение с другими примитивами - семафором, readwrite lock, которые уже есть... Бенчмарки для всех вариантов бы
withkittens
24.01.2025 12:35Мне кажется, вы неправильно поняли фичу? lock никуда не девается, плюшки появляются из-за использования System.Threading.Lock вместо object, а не потому что мы lock вручную переписываем на using, в доке есть пример (подозрительно похожий на ваш ;)
ryanl
24.01.2025 12:35"в средах с высокой конкуренцией, где требуется частая блокировка и разблокировка, System.Threading.Lock снижает издержки на переключение контекста." - Ла ла ла, дай-ка я на хабр напишу ерунды в пятницу...
cstrike
Где примеры использования? Где side-by-side сравнение кода? Простите, но статья пустая. Что-то там восхваляется, но эти фанфары ничем не подтверждены.