c# - 线程在什么情况下可以同时进入一个锁(Monitor)区域不止一次?

标签 c# .net concurrency thread-safety locking

(问题已修改):到目前为止,答案都包括单线程线性重新进入锁区域,通过递归之类的东西,您可以在其中跟踪单线程两次进入锁的步骤。但是是否有可能以某种方式使单个线程(可能来自 ThreadPool,可能是由于计时器事件或异步事件或线程进入休眠状态并在其他代码块中分别被唤醒/重用)以某种方式产生两个彼此独立的不同地方,因此,当开发人员通过阅读他们自己的代码时没有预料到它时会遇到锁重入问题?

在 ThreadPool Class Remarks ( click here ) 中,Remarks 似乎建议休眠线程在不使用时应该重用,否则会因休眠而浪费。

但是在 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.");
        }
    }
}

最佳答案

假设您有一个包含操作的队列:

public static Queue<Action> q = whatever;

假设 Queue<T>有一个方法 Dequeue返回一个 bool 值,指示队列是否可以成功出队。

假设你有一个循环:

static void Main()
{
    q.Add(M);
    q.Add(M);
    Action action;
    while(q.Dequeue(out action)) 
      action();
}
static object lockObject = new object();
static void M()
{
    Action action;
    lock(lockObject) 
    { 
        if (q.Dequeue(out action))
            action();
    }
}

很明显主线程两次进入M中的锁;此代码可重入。也就是说,它通过间接递归进入自身

您觉得这段代码难以置信吗?它不应该。 这就是 Windows 的工作原理。每个窗口都有一个消息队列,当“抽取”消息队列时,将调用与这些消息相对应的方法。当您单击一个按钮时,一条消息将进入消息队列;抽取队列时,将调用与该消息对应的点击处理程序。

因此,在编写 Windows 程序时,锁包含对泵送消息循环的方法的调用是极其常见且极其危险的。如果您首先因为处理一条消息而进入该锁,并且如果该消息在队列中两次,那么代码将间接进入自身,这可能会导致各种疯狂行为。

消除这种情况的方法是 (1) 永远不要在锁内做任何事情,即使是稍微复杂的事情,以及 (2) 当您处理消息时,禁用处理程序直到消息被处理。

关于c# - 线程在什么情况下可以同时进入一个锁(Monitor)区域不止一次?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13983753/

相关文章:

android - AsyncTask 在线程完成之前不调用 onPreExecute

c# - Asp .Net Core 如何处理区域中的错误页面

c# - 使用 XSLT 小写函数

c# - String.Format - 参数列表问题

c# - 如何使用 Roslyn 在解决方案中查找 MethodDeclarationSyntax 的所有用法

c++ - 如果有 std::barrier,为什么还要 std::latch?

c# - 使用 C# 或第 3 方库发出传真?

.net - 规范化字符串与 ToCharArray 不同

c# - EmguCV/OpenCV QueryFrame 缓慢/缓冲

c# - Azure 表存储 - 管理并发