C# Language Deadlocks (maintenir la ressource et attendre)


Exemple

Une impasse est ce qui se produit lorsque deux ou plusieurs threads attendent que chacun se termine ou libère une ressource de telle manière qu'ils attendent pour toujours.

Si thread1 maintient un verrou sur la ressource A et attend la libération de la ressource B alors que thread2 contient la ressource B et attend la libération de la ressource A, elles sont bloquées.

En cliquant sur le bouton1 pour l'exemple de code suivant, votre application entre en état d'interblocage et se bloque

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#";
            }
        }
    }
}

Pour éviter d'être bloqué de cette façon, on peut utiliser Monitor.TryEnter (lock_object, timeout_in_milliseconds) pour vérifier si un verrou est déjà contenu sur un objet. Si Monitor.TryEnter ne parvient pas à acquérir un verrou sur lock_object avant timeout_in_milliseconds, il retourne false, ce qui donne au thread la possibilité de libérer d'autres ressources et de générer des rendements, donnant ainsi la possibilité aux autres threads de se terminer comme dans cette version légèrement modifiée. :

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#";
            }
        }
    }
}

Notez que cette solution repose sur le fait que thread2 ne veut pas que ses verrous et thread1 soient prêts à céder, de sorte que thread2 a toujours la priorité. Notez également que thread1 doit refaire le travail qu'il a fait après avoir verrouillé la ressource A, quand il cède. Par conséquent, soyez prudent lorsque vous implémentez cette approche avec plusieurs threads, car vous courez le risque d’entrer dans ce que l’on appelle un livelock - un état qui se produirait si deux threads continuaient à faire le premier bit de leur travail , recommençant à plusieurs reprises.