【问题标题】:Why lock("myLock") can cause problems?为什么 lock("myLock") 会导致问题?
【发布时间】:2017-05-17 08:23:41
【问题描述】:

我在 Microsoft 的 C# 参考中阅读了以下内容:

lock("myLock") 是一个问题,因为进程中的任何其他代码 使用相同的字符串,将共享相同的锁。

具体是什么意思?

以下代码没有按我的预期工作吗? (我希望ReadyCount 是一致的)

public class Calculator
{
    public int ReadyCount { get; private set; }

    public void IncreaseReadyCount()
    {
        lock ("ReadyCount")
        {
            ReadyCount++;
        }
    }

    public void Calculate()
    {
        Parallel.ForEach(list, litItem =>
        {
            IncreaseReadyCount();
        });
    }
}

【问题讨论】:

  • 要明确:如果 all 你想做的是计数器的线程安全增量:Interlocked.Increment 是你的朋友(只要你也使用@987654324 @ 或类似的,当您想阅读它时,或 Interlocked.CompareExchange 绝对 100% 确定)

标签: c# parallel-processing locking parallel.foreach


【解决方案1】:

问题是:那个锁的范围是什么? 你不知道;它至少等同于静态/全局到您的Calculator 类型,但它也可以巧合地由任何其他任何地方的代码共享碰巧做了一个ldstr 'ReadyCount'(返回实习版本)和lock(或使用Monitor等)。不太可能,但有风险。

更重要的是,它对普通读者来说并不明显,这是一个问题。如果您打算将它作为静态/全局锁,那么这相当于您的代码,但更明显并且没有无关代码偶然获取锁的风险:

static readonly object readyCountLock = new object();
...
lock(readyCountLock) {...}

有了这个,至少它在做什么是显而易见的。


不过,就我个人而言,我很想只使用Interlocked.Increment(ref _someField) :)

【讨论】:

  • 如果我理解正确,问题是字符串文字“ReadyCount”作为全局静态对象存储在应用程序中,如果我只是在另一个锁中使用相同的字符串,它将是相同的对象实例。但这只会导致如果我在其他地方使用此字符串文字进行锁定,我的应用程序可能会慢一些。所以在我的情况下(整个应用程序中只有一个并行代码块)并没有什么不同。我已经把它替换成一个静态的只读对象,只是想完全理解。
  • @jannagy02 从技术上讲,它可能永远可用 - 另一个线程可以获取该锁并且永远不会释放它 - 但不太可能。不过,您的锁也可以在实例级别(每个 Calculator 实例)而不是全局。
【解决方案2】:

除了 Marc 所说的之外,在现实生活中,人们经常尝试锁定可能没有被保留的字符串,例如锁定数据库记录中的某个键。如果您锁定在字符串上,则仅(某种)锁定字符串有效。但是考虑一下:

// not interned, but both strings represent "test"
string lock1 = new string(new char[] { 't', 'e', 's', 't' });
string lock2 = new string(new char[] { 't', 'e', 's', 't' });
Task.Run(() =>
{
    lock (lock1) {
        Console.WriteLine("1 entered");     
        Thread.Sleep(1000);
    }
});
Task.Run(() =>
{
    lock (lock2)
    {
        Console.WriteLine("2 entered");
        Thread.Sleep(1000);
    }
});

此代码立即执行两个“受保护”部分,因为尽管两个字符串都是“测试” - 它们是不同的实例。所以锁定常量字符串是很危险的,因为它是全局的,你永远不知道还有哪些代码使用了这种“锁定”,而锁定字符串变量是危险的,因为它可能根本不起作用。

回答有关锁定ReadyCount.ToString() 的评论。这正是人们在现实生活中尝试这样做的方式(其中ReadyCount 是数据库记录或类似的某些属性)。我 ReadyCount 你的意思是一些数字,而不是真正的字符串(否则调用 ToString 没有意义)。不,这也很糟糕,因为:

int readyCount = 1;
string lock1 = readyCount.ToString();
string lock2 = readyCount.ToString();
bool same = Object.ReferenceEquals(lock1, lock2);
// nope, not the same, lock will not work

【讨论】:

  • 同意;要么您可能锁定的范围太广(实习),或者您实际上没有锁定根本(不同时间的不同值);无论哪种方式都很有趣!
  • 那么 "lock ("ReadyCount".ToString())" 也是一个好方法吗?
  • @jannagy02 不会改变任何东西 - string.ToString() 被实现为 return this; - 但是,如果它确实改变了输出,那么你会锁定一个每次都不同的实例,所以...你实际上不会在锁中做任何有用的事情(只有当竞争线程锁定同一个实例时,锁才有用)
  • @jannagy02 我已经更新了答案,假设 ReadyCount 你的意思是整数。
  • @Evk 感谢您的努力,对理解锁定很有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-11-18
  • 2021-03-12
  • 1970-01-01
  • 1970-01-01
  • 2012-01-05
  • 2017-11-21
  • 1970-01-01
相关资源
最近更新 更多