Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

Comment fonctionne lock?

Poster un commentaire

Voici un exemple d’utilisation du pattern lock extrait de MSDN:


sealed class Account
{
   public decimal Solde = 1000;
   private Object thisLock = new Object();

   public void Retirer(decimal montant)
   {
      lock (thisLock)
      {
         if (montant > Solde)
         {
            throw new Exception("Fonds insuffisants");
         }
         Solde -= montant;

         Console.WriteLine("Retrait effectué avec succès");
      }
   }
}

 

Vous êtes vous déjà demandé pourquoi il est recommandé d’injecter la variable locale private thisLock plutôt que des choses comme this ou typeof(int)?

Le plus simple est de construire un exemple qui montre le danger de ce genre de pratique.

 

On déclare la méthode supplémentaire suivante:

sealed class HardCompute
{
    private Object thisLock = new Object();

    public void CountVeryHard()
    {
        lock (thisLock)
        {
            long total = 0;
            for (long i = 0; i < int.MaxValue; i++)
            {
                total = total + 2 * i;
            }

            Console.WriteLine("Total: {0}",total);
        }
    }
}

Note: je sais, je sais, il ne viendrai à l’idée de personne de mettre un lock dans la dernière méthode. Mais j’avais besoin d’un exemple qui provoque le phénomène que je souhaite mettre en évidence à chaque lancement.
Retenez surtout que si la première méthode est très rapide, la seconde est très lente.

Pour finir on complète avec le code:

static void Main(string[] args)
{
    HardCompute hardCompute = new HardCompute();
    Task t1 = new TaskFactory().StartNew(() => hardCompute.CountVeryHard());

    Account account = new Account();
    Task t2 = new TaskFactory().StartNew(() => account.Retirer(100));

    Console.WriteLine("On attend");
    Task.WaitAll(t1, t2);

    Console.ReadLine();
}

La fenêtre d’exécution est la suivante:

09-05-2014 19-54-38On observe que certes CountVeryHard a été lancé le premier, mais c’est Retrait qui s’est terminé avant. C’est normal, les deux méthodes ont été lancées de façon asynchrone.

 

Supposons maintenant que dans les deux méthodes de calcul on pose le verrou avec par exemple:

lock(typeof(int))

Cette fois l’affichage devient:

09-05-2014 19-58-17

L’affichage est presque simultané et dans l’ordre inverse. Que s’est t’il passé?

  • CountVeryHard a posé un verrou sur typeof(int)
  • Il démarre une boucle très longue
  • La méthode ayant été lancée de façon asynchrone, on lance Retrait sans attendre le retour de CountVeryHard
  • Un verrou a déjà été posé sur typeof(int) qui est évidement la même chose que celui de CountVeryHard
  • Le thread qui exécute Retrait est bloqué jusqu’à la fin de l’exécution de CountVeryHard

 

Comme on le voit, dans cette construction on perd le côté asynchrone.

Si vous êtes intéressé par des explications bas niveau sur le fonctionnement de lock:

http://blog.coverity.com/2014/02/12/how-does-locking-work/#.U20aOHmKCpp

 

 

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s