此操作线程是否安全

本文关键字:安全 是否 线程 操作 | 更新日期: 2023-09-27 17:56:19

在下面的示例中,当单击"提交"按钮时,静态变量 Count 的值将递增。但是此操作线程安全吗?使用应用程序对象是执行此操作的正确方法吗?这些问题也适用于 Web 表单应用程序。

当我单击"提交"按钮时,计数似乎总是增加。

视图(剃刀):

@{
    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;
}

此操作线程是否安全

不,不是。+= 运算符分 3 个步骤完成:读取变量的值,将其增加 1,分配新值。扩大:

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

线程可以在其中任意两个步骤之间被抢占。这意味着,如果 Count 为 0,并且两个线程同时执行+= 50,则Count可能是 50 而不是 100。

  1. T1 Count读作 0。
  2. T2 Count读作 0
  3. T1加 0 + 50
  4. T2加 0 + 50
  5. T1分配 50 到 Count
  6. T2分配 50 到 Count
  7. 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();
} 
增量

不是原子的,所以不是线程安全的。

退房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);

如果一个方法(实例或静态)只引用该方法范围内的变量,那么它是线程安全的,因为每个线程都有自己的堆栈。还可以通过使用各种同步机制来实现线程安全。

此操作不是线程安全的,因为它使用共享变量:ViewBag.BeforeCount。

是什么使方法具有线程安全性?规则是什么?