Подтвердить что ты не робот

Безопасна ли эта операция?

В следующем примере, когда нажата кнопка "Отправить", увеличивается значение статической переменной Count. Но безопасна ли эта операция? Использует ли объект Appliation надлежащий способ выполнения такой операции? Вопросы применимы также к приложениям веб-форм.

Счет всегда увеличивается, когда я нажимаю кнопку "Отправить".

Вид (Razor):

@{
    Layout = null;
}
<html>

<body>
    <form>
        <p>@ViewBag.BeforeCount</p>
        <input type="submit" value="Submit" />
    </form>
</body>
</html>

Контроллер:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        ViewBag.BeforeCount = StaticVariableTester.Count;
        StaticVariableTester.Count += 50;
        return View();
    }     
}

Статический класс:

public class StaticVariableTester
{
    public static int Count;
}
4b9b3361

Ответ 1

Нет, нет. Оператор + = выполняется в 3 этапа: прочитайте значение переменной, увеличьте ее на единицу, назначьте новое значение. Expanded:

var count = StaticVariableTester.Count;
count = count + 50;
StaticVariableTester.Count = count;

Ни один из этих шагов может быть вытеснен нитью. Это означает, что если Count равно 0, а два потока выполняют += 50 одновременно, возможно Count будет равно 50 вместо 100.

  • T1 читает Count как 0.
  • T2 читает Count как 0
  • T1 добавляет 0 + 50
  • T2 добавляет 0 + 50
  • T1 назначает 50 на Count
  • T2 назначает 50 на Count
  • Count равно 50

Кроме того, это может быть также вытеснено между двумя вашими двумя инструкциями. Это означает, что два параллельных потока могут устанавливать ViewBag.BeforeCount в 0 и только затем увеличивать StaticVariableTester.Count.

Использовать блокировку

private readonly object _countLock = new object();

public ActionResult Index()
{
    lock(_countLock)
    {
        ViewBag.BeforeCount = StaticVariableTester.Count;
        StaticVariableTester.Count += 50;
    }
    return View();
}   

Или используйте Interlocked.Add

public static class StaticVariableTester
{
    private static int _count;

    public static int Count
    {
        get { return _count; }
    }

    public static int IncrementCount(int value)
    {
        //increments and returns the old value of _count
        return Interlocked.Add(ref _count, value) - value;
    }
}

public ActionResult Index()
{
    ViewBag.BeforeCount = StaticVariableTester.IncrementCount(50);
    return View();
} 

Ответ 2

Инкремент не является атомарным, поэтому он не является потокобезопасным.

Отъезд Interlocked.Add:

Добавляет два 32-битных целых числа и заменяет первое целое число суммой как атомную операцию.

Вы бы использовали его следующим образом:

Interlocked.Add(ref StaticVariableTester.Count, 50);

Лично я бы обернул это в ваш класс StaticVariableTester:

public class StaticVariableTester
{
    private static int count;

    public static void Add(int i)
    {
        Interlocked.Add(ref count, i);
    }

    public static int Count
    {
        get { return count; }
    }
}

Если вы хотите вернуть значения (в соответствии с комментарием dcastro), вы всегда можете:

public static int AddAndGetNew(int i)
{
     return Interlocked.Add(ref count, i);
}

public static int AddAndGetOld(int i)
{
     return Interlocked.Add(ref count, i) - i;
}

В вашем коде вы можете сделать

ViewBag.BeforeCount = StaticVariableTester.AddAndGetOld(50);

Ответ 3

Если метод (экземпляр или статический) ссылается только на переменные, охваченные этим методом, то он является потокобезопасным, потому что каждый поток имеет свой собственный стек. Вы также можете добиться безопасности потоков, используя различные механизмы синхронизации.

Эта операция не является потокобезопасной, поскольку она использует общую переменную: ViewBag.BeforeCount.

Что делает метод Thread-безопасным? Каковы правила?