为什么 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
我已阅读 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