Синхронизация операций в .NET на примерах
Всем привет. Сегодня я расскажу об инструментах, которые существуют в .NET для параллельной работы с какими-то внешними ресурсами и приведу примеры, где и как их можно применить.
При параллельной работе с каким-то ресурсом, нам нужно синхронизировать доступ к нему, чтобы не попасть в состояние гонки или банально не перегрузить его. Для этого в .NET существуют следующие вещи:
lock-object. Это самый простой способ синхронизации. Мы заводим объект, который будем использовать для блокировки параллельного выполнения какого-то участка кода.
Применять стоит, когда нам нужно, чтобы какой-то участок кода в один момент времени выполнялся только одним потоком. Тут может быть любая работа с файлами, БД и другими ресурсами.
Важно понимать, что lock работает на уровне приложения, а не ОС, поэтому другое приложение может спокойно занять наш ресурс.
Еще следует помнить, что код в lock секции должен выполняться в рамках одного потока, поэтому мы не можем использовать внутри асинхронные вызовы.
Синтаксис
Mutex. Используется для ограничения доступа к ресурсу на уровне ОС.
Его может освободить только тот поток, который его занял.
Подойдет для ограничения доступа к файлам.
Синтаксис
SemaphoreSlim. Облегченная версия семафора. Сам семафор предоставляется ОС и используется для того, чтобы ограничить число одновременных пользователей ресурса.
Работает на уровне ОС.
Можем указать, сколько одновременно потоков могут работать с ресурсом. Полезно, если мы не хотим перегрузить его, например, при обращении к сетевой карте при REST-запросах.
Слим версия может быть асинхронной, что полезно для работы с файлами, к которым мы хотим ограничить доступ. На работе некоторые настройки сервисов мы храним в .json-файлах, для ограничения доступа к ним из нескольких потоков, мы используем слим версию и асинхронное ожидание.
Синтаксис
AutoResetEvent. Как и классы выше служит для синхронизации доступа к ресурсу.
Отличие в том, что позволяет управлять одним потоком из другого.
Синтаксис
Interlcoked. Служит для произведения атомарных операций.
Подходит, если есть какая-то переменная, которую мы хотим атомарно изменять.
Еще с его помощью можно поставить флаг на какую-то часть кода, которую должен выполнять какой-то из потоков, но нам не важно какой. Тогда при первом входе в метод поток будет поднимать флаг через Interlocked, а другие потоки будут выходить из метода, когда будут видеть, что флаг уже поднят.
Пример работы с флагом через Interlocked
Это те вещи, которые я часто встречаю во время работы, и которые могут серьезно облегчить жизнь, если мы собираемся работать с каким-то ресурсом параллельно. Надеюсь кому-то эта статья поможет узнать что-то новое или разобраться с каким-то из этих способов синхронизации.