【问题标题】:Is this operation thread safe?这个操作线程安全吗?
【发布时间】:2014-04-03 11:48:52
【问题描述】:

在以下示例中,当单击“提交”按钮时,静态变量 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;
}

【问题讨论】:

  • 简答:
  • Static 变量不是线程安全的。
  • Static 和线程安全是正交的,请阅读线程安全。
  • @Bharadwaj 我的最爱thisthis。他们还不够,谷歌会给你更多:)
  • @Sriram Sakthivel 谢谢 :) “程序员永远是学习者”。 :)

标签: c# asp.net multithreading


【解决方案1】:

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

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

此外,它也可以在您的前两个指令之间被抢占。这意味着两个并发线程可能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);
    

    【讨论】:

    • 可能想要Interlocked.Add
    • 这是做什么的?他加了50!不仅仅是var++
    • 除了不是线程安全的之外,它在网络农场的情况下也不能跨服务器工作。
    • 如果Count 是一个属性,它应该是,这将不起作用。
    • @dcastro 是的,对于 OP 来说,这很公平。 MSDN 说返回值是“存储在 location1 的新值。”
    【解决方案3】:

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

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

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

    【讨论】:

    • 这个答案暗示了使用非局部变量和线程安全之间的当且仅当关系。但是,完全有可能通过各种同步机制获得引用外部作用域变量的线程安全代码。
    • @Chris Hayes,答案已更正。感谢您的评论。
    猜你喜欢
    • 1970-01-01
    • 2012-01-09
    • 1970-01-01
    • 1970-01-01
    • 2010-10-11
    • 2011-06-19
    • 2020-02-15
    相关资源
    最近更新 更多