IUnknown.Release condition de concurrence d’implémentation standard?

Voici un moyen standard (pour ne pas dire recommandé) d’implémenter la méthode Release de l’interface COM IUnknown (directement issue de MSDN):

ULONG CMyMAPIObject::Release() { // Decrement the object's internal counter. ULONG ulRefCount = InterlockedDecrement(m_cRef); if (0 == m_cRef) { delete this; } return ulRefCount; } 

Je me demandais si une condition de concurrence pouvait se produire si le modèle d’appartement n’était pas STA :

  • dire qu’il y a une référence à gauche
  • le thread 1 le libère en appelant Release
  • il fonctionne et est arrêté juste avant de delete this
  • le thread 2 est planifié et obtient une nouvelle référence à l’object, par exemple en appelant QueryInterface ou AddRef
  • thread 1 continuer à exécuter et exécute delete this
  • le thread 2 est laissé avec un object invalide

Pour moi, le seul moyen d’assurer la cohérence serait de créer un indicateur, par exemple supprimé , de verrouiller toute la section critique, c’est-à-dire toute la méthode Release , à l’exception du retour, et de définir l’indicateur sur true .

Et dans les méthodes AddRef et QueryInterface , cochez cet indicateur et, s’il est défini, rejetez la demande de nouvelle référence.

Qu’est-ce que je rate?

Merci d’avance.

le thread 2 est planifié et obtient une nouvelle référence à l’object, par exemple en appelant QueryInterface ou AddRef

Il ne peut le faire que s’il a déjà une référence à IUnknown ou à l’une des autres interfaces implémentées par l’object. Pour lequel un appel à AddRef () a été effectué précédemment. Le compte de référence ne peut donc jamais être réduit à une valeur inférieure à 1 par l’appel Release d’un autre thread.

Écrivez le code correctement, vous devez comparer ulRefCount à 0, pas à m_cRef.

Il y a une condition de concurrence dans le code, mais ce n’est pas celle de votre exemple. Cette implémentation de Release() a une condition de concurrence qui peut provoquer un comportement indéfini (généralement un “double free”). Considérer:

  1. Thread 1 & Thread 2 ont une référence à l’object ( m_cRef == 2)
  2. Le thread 1 appelle Release() et est interrompu immédiatement après avoir exécuté InterlockedDecrement() ( m_cRef == 2)
  3. Thread 2 appelle Release() et s’exécute jusqu’à la fin, donc m_cRef == 0 pour qu’il appelle delete this .
  4. Thread 1 reprend à la ligne if (0 == m_cRef) et m_cRef == 0 donc il appelle delete this nouveau provoquant un comportement indéfini (généralement une erreur “double free”).

La mise en œuvre correcte est la suivante:

 ULONG CMyMAPIObject::Release() { // Decrement the object's internal counter. ULONG ulRefCount = InterlockedDecrement(m_cRef); if (0 == ulRefCount) //<<<< THIS FIXES THE PROBLEM { delete this; } return ulRefCount; } 

La vérification if doit être sur la variable locale ulRefCount . Comme InterlockedDecrement() renvoie la valeur à laquelle il a été décrémenté, ulRefCount ne sera mis à zéro que pour l'appel de Thread 2, alors delete this le uniquement sur le thread 2.

Le if (0 == m_cRef) accède à un état partagé sans verrou et n'est pas sûr.

Voir aussi la réponse à cette question: Pourquoi cette implémentation de COM IUnknown :: Release fonctionne-t-elle?