这个操作线程安全吗?

时间:2014-04-03 11:48:52

标签: c# asp.net multithreading

在以下示例中,单击“提交”按钮时,静态变量Count的值将递增。但这个操作线程安全吗?是否正在使用Appliation对象进行此类操作?这些问题也适用于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 个答案:

答案 0 :(得分:13)

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

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

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

  1. T1Count读为0。
  2. T2Count读为0
  3. T1添加0 + 50
  4. T2添加0 + 50
  5. T1将{50}分配给Count
  6. T2将{50}分配给Count
  7. Count等于50
  8. 此外,它也可以在前两个指令之间被抢占。这意味着两个并发线程可能设置为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();
    } 
    

答案 1 :(得分:5)

增量不是原子的,因此不是线程安全的。

结帐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);

答案 2 :(得分:4)

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

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

What Makes a Method Thread-safe? What are the rules?