死鎖(保持資源和等待)

當兩個或多個執行緒等待彼此完成或以永遠等待的方式釋放資源時,會發生死鎖。

如果 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 後所做的工作。因此,在使用多個屈服執行緒實現此方法時要小心,因為你將冒著進入所謂的活鎖的風險 - 如果兩個執行緒繼續執行其工作的第一位然後相互產生則會發生這種狀態,反覆重複。