Создавая то или иное приложение, разработчик достаточно часто не подозревает об ошибках, которые он может допустить, совершая самые примитивные операции. Если речь идет о однопользовательских приложениях, то конечно такая ситуация маловероятна. Однако если программа дает возможность хотя бы двоим людям — могут возникнуть проблемы…
Самый простой и распространенный пример ситуации, описанной выше — работа с балансом. Представим ситуацию, что у нас есть некоторая система, в которой работают разные пользователи. Каждый пользователь состоит в группе. А каждая группа имеет некоторый баланс — общак, который доступен всем пользователям, состоящим в группе. Каждый пользователь может тратить средства с баланса, если сумма баланса превышает необходимую сумму снятия. Если подумать, что может быть проще? Делаем проверку что сумма меньше баланса, если да, то вычитаем потраченную сумму из баланса и работаем дальше. Примерно так:
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; } }
Как видно все очень просто, а главное - теперь безопасно!