简单的线程问题,锁定对共享资源或整个函数的访问?

时间:2011-02-06 08:31:25

标签: c# .net multithreading locking critical-section

这是对我以前的一个问题的解释。这是一个简单的线程问题,但我似乎无法理解它。

如果我有共享代码:

private static object objSync = new object();
private int a = 0;

    public void function()
    {
    lock(objSync)
      a += 2;

    lock(objSync)
      a += 3;
    Console.WriteLine(a.ToString());
    a=0;
    }

我不能指望'a'到底等于5,因为第一个线程获得一个锁将'a'设置为2,然后下一个线程可以获得一个锁,在第一个线程之前将其设置为'4'能够加3,你最终会得到7。

我理解的解决方案是锁定整个事物,然后你总是可以期待5.现在我的问题是如果两个锁之间有一百万行代码。我无法想象锁定一百万行代码。您如何确保线程安全,但不会通过锁定一百万行和两行代码来实现性能失败?

编辑:

这是我为这个问题写的无意义的代码。实际应用是线路监控系统。有两个屏幕显示当前行,一个用于职员,一个用于公众。职员的屏幕通过串行端口接受“点击”,然后通过串行端口进行“点击”,然后通过通知事件更新公共屏幕(参见观察者设计模式)。问题是,如果您向点击者发送垃圾邮件,它们就不会同步。我想是发生的事情是第一个屏幕添加到行号,显示它然后更新公共屏幕,但在公共屏幕有机会显示数据库中的行号之前,职员再次点击并且数字消失了同步。上面的'a'表示我从db检索的值,而不仅仅是一个简单的int。有一些地方我改变了价值,我需要所有这些以原子方式发生。

2 个答案:

答案 0 :(得分:4)

这一切都取决于你所定义的“成功”。如果它是一个计数器,那可能仍然是正确的结果。如果您只是想要更新它,那么在想到的情况下更新它,然后在第一个锁内部拍摄快照,同样与最终锁中的快照进行比较。在那种情况下,您可以使用Interlocked.CompareExchange替换大部分代码。同样在“计数器”场景中,Interlocked.Increment是你的朋友。

如果代码必须匹配,那么您有几个选项:

  • 如果比较失败,再次运行整百万行;重复,直到你赢得比赛(通过匹配)
  • 如果比较失败,则抛出异常
  • 阻止百万行;它可能仍然是一个快速的百万行......
  • 想一些对你的场景有意义的其他解决策略

在前两个中,您应该通过将任何相关数据保留在中间状态来监视污染事物。

顺便说一下;最终赋值为0应该是同一个锁定策略的一部分。锁只有在所有访问都遵守它们的情况下才有效。

但只有您的情景可以告诉我们冲突中的“正确”行为是什么。

答案 1 :(得分:0)

实际上这是一个设计问题。如果你的方法计算“a”的不同值,取决于某些情况,但你不希望另一个线程在一个操作处于活动状态时改变“a”的值,这样的事情更合适:

private static object objSync = new object();
private int a = 0;

    public void function()
    {
      int b;
      lock(objSync)
        b = a;
      b += 2;

      //million lines of code

      b +=3;
      lock(objSync)
      {
        a = b;
        Console.WriteLine(a.ToString());
        a=0;
      }
    }