Comportement étrange lors de la modification d'un objet exclusivement verrouillé - Monitor.Enter (x)

StackOverflow https://stackoverflow.com/questions/828014

  •  06-07-2019
  •  | 
  •  

Question

Je voulais voir ce qui se passait si vous changiez la référence d'un objet exclusivement verrouillé par Monitor.Enter (). Comme prévu, une exception SynchronizationLockException a été levée. Mais j'ai été surpris de voir plusieurs threads dépassant le moniteur avant que l'exception ne soit lancée.

Voici ce que fait le code ci-dessous.

  1. créer et démarrer 100 threads
  2. oblige tous les threads à attendre qu'un ManualResetEvent soit défini.
  3. définir le ManualResetEvent - un peu comme si vous brandissiez le drapeau vert lors d’une course Indy.
  4. définir un verrou exclusif (Monitor.Enter (x)) sur x
  5. changer la référence de x.

À ce stade, je m'attendais à une sorte d'exception, mais cela ne se produit pas avant Monitor.Exit (x). Ce qui est vraiment étrange, c'est que 10 à 20 threads semblent être capables de dépasser le verrou avant que l'exception ne soit levée. Comment ça se passe? Cela ne semble pas être le cas. Bien sûr, changer la référence de l'objet exclusivement verrouillé est un non-non. Je ne le ferais jamais dans un code réel, mais j'étais quand même surpris de voir les autres threads dépasser le moniteur. Tes pensées?

using System;
using System.Threading;

namespace ThreadingPlayground
{
  class Program
  {
    private int _value;
    object x = new object();

    ManualResetEvent _event = new ManualResetEvent(false);

    static void Main()
    {
      Program p = new Program();
      p.StartThreads();
      Console.ReadLine();
    }

    private void StartThreads()
    {

      for(int i = 0;i<100;i++)
      {
        Thread t = new Thread(IncrementValue);
        t.Start();
      }
      _event.Set();
    }

    private void IncrementValue()
    {
      WaitHandle.WaitAll(new WaitHandle[] {_event});

      Monitor.Enter(x);

      // Change the reference of the exclusively locked object. This 
      // allows other threads to enter, should this be possible?
      x = Thread.CurrentThread.ManagedThreadId.ToString();

      Console.WriteLine(++_value);

      // throws a SynchronizationLockException 
      // but not until 10 - 20 more lines are written
      Monitor.Exit(x);
    }
  }
}

 Sortie de la console, on dirait que certains threads ont dépassé le moniteur ??

Était-ce utile?

La solution

Ce que vous voyez est le comportement attendu. La variable utilisée pour passer une référence dans Monitor.Enter () n'a rien de spécial. La modification de la référence ne doit pas empêcher les autres threads d’acquérir un verrou exclusif, car la variable a une nouvelle valeur et cette référence n’est verrouillée nulle part.

Votre problème est livré avec Exit , car le thread appelant Exit ne dispose pas d'un verrou exclusif sur la référence transmise. Un autre thread peut également être verrouillé. le fil que vous exécutez ne le fait pas.

C’est pourquoi, comme vous le savez, il est toujours préférable de verrouiller avec une variable dont la référence ne changera jamais. Si la variable de votre ressource est susceptible de changer, utilisez une nouvelle référence.

Est-ce assez clair?

Autres conseils

'x' est une référence à un objet; ce n'est pas l'objet lui-même. Quand vous faites

      x = Thread.CurrentThread.ManagedThreadId.ToString();

Vous avez jeté l'objet verrouillé auquel x faisait référence précédemment et vous avez fait référence à un autre objet. Maintenant, quand vous faites

      Monitor.Exit(x);

Vous obtenez une exception car cet objet n'est pas verrouillé. L'objet que vous avez verrouillé est maintenant un déchet à récupérer par le ramasse-miettes.

La raison de ce problème est que vous modifiez la valeur de x ici:

x = Thread.CurrentThread.ManagedThreadId.ToString();

Donc quand vous arrivez à

Monitor.Exit(x)

vous libérez le verrou avec un autre objet. C'est comme si vous mettiez un cadenas avec une clé et essayez de retirer le cadenas avec la clé d'un autre cadenas.

De plus, Console.Writeline est une instruction coûteuse comparée aux autres. Plusieurs threads doivent donc entrer dans le moniteur avant que l’un d’eux ne se rapproche de la ligne d’arrivée.

Voici un exemple d'exécution:

Vous commencez un tas de threads.

  • Le fil T1 acquiert le verrou avec l'objet x .
  • T2 tente d'acquérir un verrou avec l'objet x . Ce fil n'a pas de chance, car nous savons qu'il va attendre pour toujours.
  • T1 modifie x . C'est maintenant un nouvel objet. Je l'appellerai x'1 .
  • T3 tente d'acquérir le verrou. Mais la variable x fait en réalité référence à l'objet x'1 . Personne n'a verrouillé x'1 , donc il passe.
  • T3 modifie x . C'est maintenant un nouvel objet appelé x'3 .
  • T1 écrit sur la console.
  • T3 écrit sur la console.
  • T1 tente de libérer le verrou avec la variable x , qui pointe vers l'objet ... x'3 .
  • L'objet Moniteur dit: "Hé, que pensez-vous faire?" Tu n'as pas ce verrou! Mangez cette exception , petit meunier "
  • Dérive
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top