我写过很多多线程 C# 代码,在我发布的任何代码中从未出现过死锁。
我使用以下经验法则:
- 我倾向于只使用
lock
关键字(我也使用其他技术,例如读取器/写入器锁,但很少使用,并且仅在需要提高速度时使用)。 - 如果我正在处理一个
long
,我会使用Interlocked.Increment
。 - 我倾向于使用最小粒度的锁定单元:我只倾向于锁定原始数据结构,例如
long
、dictionary
或list
.
我想知道是否有可能产生死锁如果这些规则始终得到遵守,如果是这样,代码会是什么样子?
更新
我也使用这些经验法则:
- 避免在任何可能无限期暂停的事物周围添加锁,尤其是 I/O 操作。如果您绝对必须这样做,请确保锁中的所有内容在设置的
TimeSpan
后绝对超时。 - 我用于锁定的对象始终是专用对象,例如
object _lockDict = new object();
然后lock(_lockDict) {//在此处访问字典 }
。
更新
Jon Skeet 的出色回答。这也证实了为什么我永远不会遇到死锁,因为我倾向于本能地避免嵌套锁,即使我确实使用它们,我也总是本能地保持输入顺序一致。
为了回应我关于倾向于只使用 lock
关键字的评论,即使用 Dictionary
+ lock
而不是 ConcurrentDictionary
,Jon Skeet 发表了评论:
@Contango: That's exactly the approach I'd take too. I'd go for simple code with locking over "clever" lock-free code every time, until there's evidence that it's causing an issue.
最佳答案
是的,很容易死锁,而无需实际访问任何数据:
private readonly object lock1 = new object();
private readonly object lock2 = new object();
public void Method1()
{
lock(lock1)
{
Thread.Sleep(1000);
lock(lock2)
{
}
}
}
public void Method2()
{
lock(lock2)
{
Thread.Sleep(1000);
lock(lock1)
{
}
}
}
同时调用 Method1
和 Method2
,然后 boom - 死锁。每个线程都将等待“内部”锁,其他线程已将其作为其“外部”锁获取。
如果您确保始终以相同的顺序获取锁(例如“永远不要获取 lock2
,除非您已经拥有 lock1
)并以相反的顺序释放锁(如果您使用 lock
获取/释放,这是隐式的)那么您将不会遇到那种死锁。
您仍然可以在异步代码中遇到死锁,只涉及一个线程——但这也涉及Task
:
public async Task FooAsync()
{
BarAsync().Wait(); // Don't do this!
}
public async Task BarAsync()
{
await Task.Delay(1000);
}
如果您从 WinForms 线程运行该代码,您将在单个线程中死锁 - FooAsync
将阻塞 BarAsync
返回的任务,并且继续BarAsync
将无法运行,因为它正在等待返回到 UI 线程。基本上,您不应该从 UI 线程发出阻塞调用...
关于c# - 如果在原始数据访问周围只使用 lock 关键字,是否有可能在 C# 中创建死锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31192914/