为什么 lock("myLock") 会导致问题?

Why lock("myLock") can cause problems?

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

lock("myLock") is a problem because any other code in the process using the same string, will share the same lock.

具体是什么意思?

下面的代码是不是没有像我预期的那样工作? (我希望 ReadyCount 是一致的)

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

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

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

问题是:那把锁的作用域是什么? 你看不出来;它 至少 等同于 static/global 你的 Calculator 类型 ,但它也可能被 任何地方的任何其他代码恰好执行ldstr 'ReadyCount'(返回内部版本)和lock(或使用Monitor等)。不太可能,但有风险。

更重要的是,它 对普通人来说 并不明显 reader,这是一个问题。如果您 打算 它是一个 static/global 锁,那么这等同于您的代码,但更明显并且没有无关代码偶然获取锁的风险:

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

有了这个,至少可以清楚的知道它在做什么。


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

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

// 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);
    }
});

此代码立即执行两个 "protected" 部分,因为尽管两个字符串都是 "test" - 它们是不同的实例。所以锁定常量字符串是危险的,因为它是全局的,你永远不知道还有哪些代码使用这样的 "lock",锁定字符串变量是危险的,因为它可能根本不起作用。

回答关于锁定 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