lock(this): A ne pas faire!

Julien on avr 5th 2008

J'ai vu ce genre de choses dans plusieurs bouts de code récemment:

  1. lock(this)
  2. {
  3. // Do stuff...
  4. }

Même si cela fonctionne, c'est une très mauvaise idée! Vous ne devriez jamais (ou du moins je n'ai aucune bonne raison en tête) faire un lock sur un type public, ce qui inclue évidemment le "this". Il y a une raison très simple à cela: vous n'avez aucun moyen de maitriser ce que le code appelant va essayer de faire.

Prenons un exemple simple. Dans le code suivant, nous avons une class qui utilise un lock sur "this" (On l'appellera ClassThatLocksItself). Nous avons aussi une seconde class (CallerClass) qui va appeler la première. CallerClass va alors utiliser un lock sur l'instance de ClassThatLocksItself lors de cet appel.

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. ClassThatLocksItself myObj = new ClassThatLocksItself();
  6.  
  7. CallerClass caller = new CallerClass(myObj);
  8. caller.LockTheObjectInAThread();
  9. Thread.Sleep(500);
  10.  
  11. myObj.LockMe();
  12. }
  13. }
  14.  
  15. class CallerClass
  16. {
  17. private ClassThatLocksItself _myObj;
  18. public CallerClass(ClassThatLocksItself myObj)
  19. {
  20. _myObj = myObj;
  21. }
  22.  
  23. public void LockTheObjectInAThread()
  24. {
  25. ThreadPool.QueueUserWorkItem(LockTheObject);
  26. }
  27.  
  28. public void LockTheObject(object state)
  29. {
  30. Console.WriteLine("Acquiring lock on the object");
  31. lock (_myObj)
  32. {
  33. Thread.Sleep(100000); // Do a long computation
  34. }
  35. Console.WriteLine("Releasing lock on the object");
  36. }
  37. }
  38.  
  39. public class ClassThatLocksItself
  40. {
  41. public void LockMe()
  42. {
  43. Console.WriteLine("ClassThatLocksItself -- Trying to acquire lock on this");
  44. lock (this)
  45. {
  46. Console.WriteLine("ClassThatLocksItself -- lock on this acquired");
  47. }
  48. Console.WriteLine("ClassThatLocksItself -- lock on this released");
  49. }

Si vous essayez d'exécuter ce code, vous remarquerez que Console.WriteLine("ClassThatLocksItself -- lock on this acquired"); est seulement executé lorsque la fonction LockTheObject() retourne (dans notre cas, après 100 secondes!). En utilisant lock(this), vous êtes devenu dépendant de facteurs externes inconnus (dans notre exemple, CallerClass a décidé d'utiliser l'instance de CLassThatLocksItself pour la synchronisation, ce que ClassThatLocksItself n'avait pas moyen de savoir). La situation est même pire pour les développeurs qui veulent réutiliser votre ClassThatLocksItself: a moi de parcourir la source de la classe, ils n'ont aucun moyen de savoir qu'il peut y avoir de grave problèmes de synchronisation.

Maintenant, essayez de deviner ce qui va se passer si l'on modifie l'implémentation de LockTheObject() par la suivante:

  1. public void LockTheObject(object state)
  2. {
  3. Console.WriteLine("Acquiring lock on the object");
  4. lock (_myObj)
  5. {
  6. _myObj.LockMe(); // will not lock!
  7. }
  8. Console.WriteLine("Releasing lock on the object");
  9. }

Un certain nombre d'entre vous devrait parier sur le deadlock je suppose. Mais non! En fait, la CLR est assez intélligente pour détecter que vous avez déjà acquis le lock sur le même objet lorsque LockMe execute lock(this). N'oubliez pas, nous sommes dans le même thread! Cela rend lock(this) très subtile: vous n'aurez des problèmes que dans des cas très spécifiques!.

Filed in .NET | No responses yet

Trackback URI | Comments RSS

Leave a reply