死锁(保持资源和等待)

当两个或多个线程等待彼此完成或以永远等待的方式释放资源时,会发生死锁。

如果 thread1 持有对资源 A 的锁定并且正在等待资源 B 被释放而 thread2 持有资源 B 并且正在等待资源 A 被释放,则它们将被解锁。

单击 button1 以获取以下示例代码将导致应用程序进入上述死锁状态并挂起

private void button_Click(object sender, EventArgs e)
{
    DeadlockWorkers workers = new DeadlockWorkers();
    workers.StartThreads();
    textBox.Text = workers.GetResult();
}

private class DeadlockWorkers
{
    Thread thread1, thread2;

    object resourceA = new object();
    object resourceB = new object();

    string output;

    public void StartThreads()
    {
        thread1 = new Thread(Thread1DoWork);
        thread2 = new Thread(Thread2DoWork);
        thread1.Start();
        thread2.Start();
    }

    public string GetResult()
    {
        thread1.Join();
        thread2.Join();
        return output;
    }

    public void Thread1DoWork()
    {
        Thread.Sleep(100);
        lock (resourceA)
        {
            Thread.Sleep(100);
            lock (resourceB)
            {
                output += "T1#";
            }
        }
    }

    public void Thread2DoWork()
    {
        Thread.Sleep(100);
        lock (resourceB)
        {
            Thread.Sleep(100);
            lock (resourceA)
            {
                output += "T2#";
            }
        }
    }
}

为避免以这种方式死锁,可以使用 Monitor.TryEnter(lock_object,timeout_in_milliseconds)来检查对象上是否已锁定。如果 Monitor.TryEnter 在 timeout_in_milliseconds 之前没有成功获取 lock_object 上的锁,则返回 false,使线程有机会释放其他持有的资源并让步,从而使其他线程有机会完成,如上面的略微修改版本:

private void button_Click(object sender, EventArgs e)
{
    MonitorWorkers workers = new MonitorWorkers();
    workers.StartThreads();
    textBox.Text = workers.GetResult();
}

private class MonitorWorkers
{
    Thread thread1, thread2;

    object resourceA = new object();
    object resourceB = new object();

    string output;

    public void StartThreads()
    {
        thread1 = new Thread(Thread1DoWork);
        thread2 = new Thread(Thread2DoWork);
        thread1.Start();
        thread2.Start();
    }

    public string GetResult()
    {
        thread1.Join();
        thread2.Join();
        return output;
    }

    public void Thread1DoWork()
    {
        bool mustDoWork = true;
        Thread.Sleep(100);
        while (mustDoWork)
        {
            lock (resourceA)
            {
                Thread.Sleep(100);
                if (Monitor.TryEnter(resourceB, 0))
                {
                    output += "T1#";
                    mustDoWork = false;
                    Monitor.Exit(resourceB);
                }
            }
            if (mustDoWork) Thread.Yield();
        }
    }

    public void Thread2DoWork()
    {
        Thread.Sleep(100);
        lock (resourceB)
        {
            Thread.Sleep(100);
            lock (resourceA)
            {
                output += "T2#";
            }
        }
    }
}

请注意,此解决方法依赖于 thread2 对其锁和 thread1 愿意产生的顽固性,因此 thread2 始终优先。还要注意,当它生成时,thread1 必须重做锁定资源 A 后所做的工作。因此,在使用多个屈服线程实现此方法时要小心,因为你将冒着进入所谓的活锁的风险 - 如果两个线程继续执行其工作的第一位然后相互产生则会发生这种状态,反复重复。