【问题标题】:When to use the lock thread in C#?什么时候在 C# 中使用锁线程?
【发布时间】:2012-02-01 23:49:16
【问题描述】:

我有一个服务器,它处理多个传入的套接字连接并创建 2 个不同的线程,以 XML 格式存储数据。

我几乎在每个异步调用的事件处理程序和代码不同部分的 2 个线程中都使用了 lock 语句来保证线程安全。遗憾的是,使用这种方法我的应用程序明显变慢了。

我尝试完全不使用lock,服务器执行速度非常快,甚至文件存储似乎也有所提升;但是程序在 30 秒 - 1 分钟后由于我不明白的原因而崩溃。工作。

所以。我认为最好的方法是使用更少的锁或仅在绝对必要的地方使用它。因此,我有两个问题:

  1. 仅当我写入公共访问变量(C# 列表)或什至读取它们时是否需要锁定?

  2. 是否只有在由套接字处理程序创建的异步线程或其他地方也需要锁?

有人可以给我一些实用的指导,关于如何操作。这次我不会发布整个代码。发布大约 2500 行代码是没有意义的。

【问题讨论】:

  • 不够具体,请在此处查看一般信息:albahari.com/threading
  • 有各种可能的程序 - 从绝对错误/极快到始终正确/相当慢。你的实验似乎达到了两端。线程安全是一个重要的话题,所以我建议阅读它而不是仅仅试图获得“实用指南”。 IE。 eulerfx 的链接是很好的起点。

标签: c# multithreading optimization thread-safety


【解决方案1】:

仅当我写入公共访问变量(C# 列表)或什至读取它们时是否需要锁定?

是的(即使您阅读了)。

是否只有在套接字处理程序创建的异步线程或其他地方也需要锁?

是的。无论代码在何处访问共享的代码段,始终锁定。


这听起来您可能没有锁定单个对象,而是锁定一件事适用于所有锁定情况。

如果是这样的话,通过创建单独的唯一对象来放入智能离散锁,这些对象一次仅关联和锁定某些部分,不会干扰其他线程中的其他线程部分。

这是一个例子:

// This class simulates the use of two different thread safe resources and how to lock them
// for thread safety but not block other threads getting different resources.
public class SmartLocking
{
    private string StrResource1 { get; set; }
    private string StrResource2 { get; set; }

    private object _Lock1 = new object();
    private object _Lock2 = new object();

    public void DoWorkOn1( string change )
    {
        lock (_Lock1)
        {
            _Resource1 = change;
        }
    }

    public void DoWorkOn2( string change2 )
    {
        lock (_Lock2)
        {
            _Resource2 = change2;
        }
    }
}

【讨论】:

  • 嘿家伙..你救了我的命..我在不同的情况下使用了不同的锁(带有不同的静态对象),现在应用程序就像一个魅力。非常感谢。
  • 我很惊讶这个帖子被锁定了!没有双关语的意思。锁的不当使用会导致事实上的竞争条件,在我看来就是这种情况。很高兴我的回答能帮到你。
  • @ClaudioFerraro:这很好,但您现在可能正在用一个问题(性能不佳)换取另一个问题(死锁)。你可能会遇到这样一种情况,线程 1 取出锁 A 并等待锁 B,线程 2 取出锁 B 并等待锁 A,当然它们都将永远等待。当您向程序添加更细粒度的锁定时,您必须为所有锁定建立严格的排序协议。例如,您必须说“我永远不会允许任何线程在 它已经获得锁 2 之后请求锁 1”。你的问题才刚刚开始;这很难。
  • 是的,锁层次结构确实避免了这个问题。但即使没有它:死锁也很容易调试 - 获取线程当前持有的所有锁的列表以及它们正在等待哪些锁,瞧,您不仅可以获得导致死锁的确切锁,还可以获得导致死锁的堆栈跟踪给它。可能是一个应用程序中最好的多线程错误;)
  • 从您的博客这里添加示例会使这个答案更好。
【解决方案2】:

基本上这可以很简单地回答:

您需要锁定由不同线程访问的所有内容。它实际上并不重要,如果它是关于阅读或写作。如果您正在读取,而另一个线程同时覆盖数据,则读取的数据可能无效,您可能正在执行无效操作。

【讨论】:

    【解决方案3】:

    当您访问成员(读取或写入)时始终使用锁定。如果您正在迭代一个集合,并从另一个线程中删除项目,那么很快就会出错。

    一个建议是当您想要迭代一个集合时,将所有项目复制到一个新集合中,然后迭代该副本。即

    var newcollection; // Initialize etc.
    lock(mycollection)
    {
      // Copy from mycollection to newcollection
    }
    
    foreach(var item in newcollection)
    {
      // Do stuff
    }
    

    同样,仅在您实际写入列表时使用锁。

    【讨论】:

      【解决方案4】:

      在没有交叉路口的情况下,您曾经在红灯时坐在汽车或公共汽车上吗?太浪费时间了吧?锁就像一个完美的红绿灯。除非路口有车流,否则它始终是绿色的。

      你的问题是“我在交通上等红灯的时间太长了。我应该直接闯红灯吗?或者更好的是,我应该完全关掉红灯,让每个人都以高速公路的速度驶过十字路口,而无需任何红灯。路口控制?”

      如果您在使用锁时遇到性能问题,那么删除锁是您应该做的最后 件事。 您正等红灯,正是因为十字路口有交叉车辆。如果没有竞争,锁非常快

      如果不先消除交叉路口,就无法消除灯光。因此,最好的解决方案是消除交叉交通。如果锁永远不会被争用,那么您将永远不会等待它。弄清楚为什么交叉路口要花这么多时间在交叉路口;不要移除灯,希望没有碰撞。会有的。

      如果你不能这样做,那么添加更细粒度的锁有时会有所帮助。也就是说,也许城里的每条道路都在同一个十字路口汇合。也许你可以把它分成两个交叉点,这样代码就可以同时通过两个不同的交叉点。

      请注意,让汽车更快(获得更快的处理器)或让道路更短(消除代码路径长度)通常会使问题在多线程场景中变得更糟。就像在现实生活中一样;如果问题是堵车,那么购买更快的汽车并在较短的道路上驾驶它们会使他们更快地进入交通堵塞,但不会更快地摆脱它。

      【讨论】:

      • +1,我总是喜欢这些隐喻式的答案。让事情更容易理解。
      • 感谢您的回答。你的答案很清楚,但我的意图是问:“如果在现实生活中人们发明了交通圈,我怎么能确定他们应该有多大才能获得更好的性能。所以我只是期待一个更具体的答案。只是“圈应该比我妻子的戒指大 100 倍”或类似的东西!
      【解决方案5】:

      阅读时需要加锁的原因是:

      假设您正在对一个属性进行更改,并且在线程处于锁之间时它已被读取两次。一次是在我们进行任何更改之前,另一次是在之后,那么我们将得到不一致的结果。

      希望对你有帮助

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多