【发布时间】:2013-01-21 03:34:23
【问题描述】:
(问题已修改):到目前为止,答案都包括单个线程通过递归等方式线性重新进入锁定区域,您可以在其中跟踪单个线程两次进入锁定的步骤。但是是否有可能以某种方式生成单个线程(可能来自 ThreadPool,可能是由于计时器事件或异步事件或线程进入睡眠状态并分别在其他代码块中被唤醒/重用)以某种方式产生两个彼此独立的不同地方,因此,当开发人员通过阅读自己的代码没有预料到时,就会遇到锁重入问题?
在 ThreadPool 类备注 (click here) 中,备注似乎建议在休眠线程不使用时应重用它们,否则会因休眠而浪费。
但在 Monitor.Enter 参考页面 (click here) 上,他们说 “同一线程在不阻塞的情况下多次调用 Enter 是合法的。” 所以我认为必须有我应该小心避免的事情。它是什么?一个线程怎么可能两次进入同一个锁区?
假设您有一些锁定区域需要很长时间。这可能是现实的,例如,如果您访问一些已被分页(或其他)的内存。锁定区域中的线程可能会进入睡眠状态或其他什么。同一个线程是否有资格运行更多代码,而这些代码可能会意外进入同一个锁定区域?在我的测试中,以下内容不会让同一线程的多个实例运行到同一锁定区域。
那么问题是如何产生的呢?您究竟需要注意避免什么?
class myClass
{
private object myLockObject;
public myClass()
{
this.myLockObject = new object();
int[] myIntArray = new int[100]; // Just create a bunch of things so I may easily launch a bunch of Parallel things
Array.Clear(myIntArray, 0, myIntArray.Length); // Just create a bunch of things so I may easily launch a bunch of Parallel things
Parallel.ForEach<int>(myIntArray, i => MyParallelMethod());
}
private void MyParallelMethod()
{
lock (this.myLockObject)
{
Console.Error.WriteLine("ThreadId " + Thread.CurrentThread.ManagedThreadId.ToString() + " starting...");
Thread.Sleep(100);
Console.Error.WriteLine("ThreadId " + Thread.CurrentThread.ManagedThreadId.ToString() + " finished.");
}
}
}
【问题讨论】:
-
第一:问一个新问题而不是修改一个旧问题会更好。其次,“是否有可能以某种方式在两个不同的地方独立地生成一个线程?”不;线程的定义是它是单点控制。如果您有两个独立的控制点,那么您就有两个线程。 (这忽略了一些涉及“轻量级线程”的微妙之处,也就是“纤维”;这是一个相当高级的话题,你现在不需要关心。)
-
@EricLippert 你的评论在我看来是最好的“答案”。 ThreadPool 线程不能仅仅因为它们进入睡眠状态而在其他地方重用;他们需要在重新使用之前完成。在锁定区域中花费很长时间的线程没有资格在其他一些独立的控制点运行更多代码。体验锁重入的唯一方法是通过递归或执行锁内的方法或委托来重入锁。如果您想发布类似的内容作为答案,我会将其标记为已接受的答案。
-
好吧,总是有 APC。请参阅 KERNEL32 的 SleepEx 函数的 MSDN 文档。 AFAIK Thread.Sleep 将线程置于警报状态,就像任何 IO 函数一样,因此睡眠或等待线程可能会执行 APC。然而,APC 很少用作通用回调,所以我想说这导致“你的”代码执行的可能性非常小。
标签: c# .net concurrency thread-safety locking