C#: Поговорм о lock или «код для одного потока»

Создавая то или иное приложение, разработчик достаточно часто не подозревает об ошибках, которые он может допустить, совершая самые примитивные операции. Если речь идет о однопользовательских приложениях, то конечно такая ситуация маловероятна. Однако если программа дает возможность хотя бы двоим людям — могут возникнуть проблемы…

Самый простой и распространенный пример ситуации, описанной выше — работа с балансом. Представим ситуацию, что у нас есть некоторая система, в которой работают разные пользователи. Каждый пользователь состоит в группе. А каждая группа имеет некоторый баланс — общак, который доступен всем пользователям, состоящим в группе. Каждый пользователь может тратить средства с баланса, если сумма баланса превышает необходимую сумму снятия. Если подумать, что может быть проще? Делаем проверку что сумма меньше баланса, если да, то вычитаем потраченную сумму из баланса и работаем дальше. Примерно так:

private double _groupAmount = 100.00;
//....
private bool Buy(double amount)
{
   if(_groupAmount < amount)
   {
     return false;
   }
   _groupAmount -= amount;
   return true;
}

Вроде бы все хорошо. Однако если вспомнить о том, что пользователи используют программу одновременно, стоит подумать о том, что есть доля вероятности, что несколько пользователей захотят выполнить покупку одновременно, и есть некоторая доля вероятности, что баланс сможет стать отрицательным - что само по себе означает ошибку (точнее баг) в приложении.
Если кому то непонятно как это возможно, рассмотрим такую ситуацию. Допустим баланс группы сейчас 99 единиц, случается так, что два пользователя в одно и тоже время хотят списать с аккаунта по 50 единиц. Как подсказывает логика один пользователь сможет совершить покупку, а другой нет. Однако если случится так что запрос второго пользователя дойдет до проверки "_groupAmount < amount" в тот момент, когда запрос первого пользователя еще не выполнил строчку "_groupAmount -= amount;" мы получаем ситуацию, когда покупку совершат оба пользователя, а баланс станет равен -1. Конечно поймать такой момент крайне сложно, однако с ростом числа пользователей вероятность ситуации будет увеличиваться. Для того что бы избежать подобного неприятного момента используется безопасный (важный) участок кода - это участок кода в котором может находится только один поток, остальные потоки выстраиваются в очередь. Для создания важного кода в C# используется ключевое слово lock, который в качестве параметра получает некий объект - выполняющий роль флага. В зависимости от флага, будут блокироваться различные участки программы, однако мы рассмотрим самый простой пример с отдельно созданным флагом в рамках каждой группы.

private double _groupAmount = 100.00;
private object _operation = new object();
//....
private bool Buy(double amount)
{
   lock(_operation)
   {
     if(_groupAmount < amount)
     {
       return false;
     }
     _groupAmount -= amount;
     return true;
  }
}

Как видно все очень просто, а главное - теперь безопасно!

Запись опубликована в рубрике C# с метками , , , , , , , . Добавьте в закладки постоянную ссылку.