Win32, threads, messages et blocages

Win32, threads, messages et blocages - C++ - Programmation

Marsh Posté le 01-07-2006 à 14:30:32    

Bonjour.
 
Je suis confronté à un cas d'inter-blocage de threads dans le cadre d'un projet professionnel (il faut donc trouver une solution assez vite).
 
Exposé du problème:
 
Le thread principal A crée un thread auxiliaire B au démarrage (en fait il en crée de multiples copies mais le fonctionnement est le même).
Le thread B fait appel à SendMessage(hWndOwnedByThreadA, ...) régulièrement.
Le thread A peut être amené à terminer PROPREMENT l'exécution du(es) thread(s) B. Pour cela, le thread A positionne un booléen à TRUE qui a pour effet de mettre fin à la boucle de traitement du thread B. Puis le ThreadA attend que le thread B se termine. Le problème : si le thread B est en en train d'exécuter un SendMessage, les deux threads sont morts !
 
En code ça donne (à peu près):
 

Code :
  1. --------- Thread A ---------------
  2. // ** création du thread B **
  3. ThreadBData *data = new ThreadBData;
  4. data->pTerminateThread = &m_bTerminateThread; // m_bTerminateThreads est à FALSE
  5. // handle de la fenêtre cible appartenant au thread A pour le SendMessage exécuté dans le thread B
  6. data->hWnd = hWndToSendMessageTo;
  7. // ... autres initialisations de data
  8. // Crée le thread et l'exécute dans la foulée (pas suspendu)
  9. HANDLE hThreadB = CreateThread(ThreadBProc, (PVOID)pData, ...)
  10. // ** Terminaison du thread B **
  11. m_bTerminateThread = TRUE;
  12. ::WaitForSingleObject(m_hThreadB, INFINITE); // blocage potientiel ici !!
  13. // ici, le thread B est terminé
  14. ---------- Thread B -----------
  15. UINT ThreadBProc(PVOID pParam)
  16. {
  17.    ThreadBData *pData = (ThreadBData*)pParam;
  18.    BOOL *pTerminateThread = pData->pTerminateThread;
  19.    HWND hWndTarget = pData->hWnd;
  20.    delete pData;
  21.    while (!(*pTerminateThread))
  22.    {
  23.       if (anEventOccured())
  24.       {
  25.           SendMessage(hWndTarget, WM_THE_EVENT, ...); // si le thread auquel appartient hWndTarget est en attente, je suis mort !
  26.       }
  27.       ::Sleep(50); // dors un peu ; pas besoin de stresser la machine
  28.    }
  29.    // fait les libérations d'usage avant la fin du thread
  30.    return 0;
  31. }


 
La solution d'ajouter un timeout ne me plait pas. Il est hors de question de tuer le thread : celà pourrait avoir des conséquences fâcheuses. Je pense qu'en utilisant des objets de synchronisation type Event, la problématique sera exactement la même : l'objet du blocage serait alors le point de synchronisation.
 
Autre détail important : on ne peut pas remplacer SendMessage par une autre instruction (type SendNotifyMessage, PostMessage, etc) car l'ordre de traitement des messages est primordial et en pratique l'ordre n'est plus respecté voir les messages sont interprétés différement (messages fantômes, codes de message erronés ou intervertis) !


Message édité par slash33 le 01-07-2006 à 14:48:40
Reply

Marsh Posté le 01-07-2006 à 14:30:32   

Reply

Marsh Posté le 02-07-2006 à 00:22:04    

Citation :

Puis le ThreadA attend que le thread B se termine. Le problème : si le thread B est en en train d'exécuter un SendMessage, les deux threads sont morts !


 
Bloqués ou morts ?

Reply

Marsh Posté le 02-07-2006 à 12:18:42    

bloqués : les threads existent toujours mais leur execution est suspendue.

Reply

Marsh Posté le 02-07-2006 à 12:40:51    

deadlock...
tu peux faire des messages asynchrones pour le send message ?


---------------
What if I were smiling and running into your arms? Would you see then what I see now?  
Reply

Marsh Posté le 02-07-2006 à 13:12:35    

Si je suis certain qu'ils seront exécutés DANS L'ORDRE D'EMISSION oui, sinon non. Mais j'ai indiqué qu'en pratique des appels à PostMessage() font échouer la logique du programme.


Message édité par slash33 le 02-07-2006 à 13:12:51
Reply

Marsh Posté le 02-07-2006 à 13:20:47    

les messages asynchrones sont en principe effectué dans l'ordre d'émission... (venant d'un seul process)


---------------
What if I were smiling and running into your arms? Would you see then what I see now?  
Reply

Marsh Posté le 02-07-2006 à 13:42:05    

Je le pensais aussi mais visiblement cela pose des désordres importants au traitement des messages. Le principe de fonctionnement du logiciel est de modifier l'aspect de l'interface graphique à la réception d'un message. La mise à jour de l'interface fait elle-même appel au mécanisme de files de messages. Mais je pense pas que cela pose un problème.


Message édité par slash33 le 02-07-2006 à 13:44:42
Reply

Marsh Posté le 02-07-2006 à 14:25:25    

déplaçe ton positionnement de booléen  de manière a ce que A continue de traiter la queue de messages quelque temps pour bien consommer tous les messages.

Reply

Marsh Posté le 02-07-2006 à 17:13:47    

Tu proposes de positionner le booléen à TRUE le plus tôt possible avant le WaitForSingleObject c'est ça ?
 
En pratique, le code réel est court. Qu'est-ce qui garanti à coup sûr que A sera toujours en état de traiter les messages envoyés par B avant que celui-ci ne se termine ?

Reply

Marsh Posté le 02-07-2006 à 17:33:33    

je penses que tu as un blocage parceque tout ce qui est produit dans la queue de messages n'est pas consommé.

Reply

Marsh Posté le 02-07-2006 à 17:33:33   

Reply

Marsh Posté le 02-07-2006 à 18:01:44    

Ben oui cela s'explique simplement : le Thread A qui consomme les messages est mis en attente (suspendu) en attendant que le Thread B se termine. Mais le thread B peut produire un message à destination de A avant de se terminer. Hors avec cette implémentation, le message produit par B doit être consommé par A pour rendre la main à B après l'exécution de SendMessage. Bref SendMessage est bloquant tant que le message n'a pas été traité (cf MSDN).
 
Ce problème est connu dans la litterature Windows sous le terme "message deadlocks". Je cherche la solution pour éviter ce blocage sur un code que je viens de prendre en main et qui fait tourner une usine!!


Message édité par slash33 le 02-07-2006 à 18:02:52
Reply

Marsh Posté le 02-07-2006 à 18:11:40    

Tu as envisagé SendMessageTimeout ?

Reply

Marsh Posté le 02-07-2006 à 18:23:54    

bin mets nous le code de la boucle de message de A.

Reply

Marsh Posté le 02-07-2006 à 19:59:56    

Trap D a écrit :

Tu as envisagé SendMessageTimeout ?


Oui effectivement. Cependant cette solution n'a pas été retenue par le groupe de dév (3/4 personnes)
 
Penses tu que je devrais défendre quand même cette solution ?


Message édité par slash33 le 02-07-2006 à 20:04:35
Reply

Marsh Posté le 02-07-2006 à 20:02:08    

bjone a écrit :

bin mets nous le code de la boucle de message de A.


Impossible pour deux raisons:
- je n'ai pas le droit de diffuser le code en dehors de l'entreprise (d'ailleurs je ne l'ai pas sur moi  :o )
- parce que l'IHM est codée en MFC et que la boucle de message est donc gérée par le modèle MFC d'interception des messages.
 
Quel(s) élément(s) de compréhension apporterait la connaissance de ce code ? :(


Message édité par slash33 le 02-07-2006 à 20:03:40
Reply

Marsh Posté le 02-07-2006 à 20:14:18    

bin déjà savoir que c'est du MFC ça aide.
ça permet de réfléchir à la chose.

Reply

Marsh Posté le 02-07-2006 à 20:17:38    

tu as donc quoi, un .cpp pour la classe de l'application, des .cpp avec les classes des fenêtres, dans l'un de tout ça ou un .cpp a part, tu as tes routines de threads ?

Reply

Marsh Posté le 02-07-2006 à 20:20:32    

Code :
  1. // Crée le thread et l'exécute dans la foulée (pas suspendu)
  2. HANDLE hThreadB = CreateThread(ThreadBProc, (PVOID)pData, ...)


 
il se passe bien quelque chose entre les deux non ?
 

Code :
  1. // ** Terminaison du thread B **
  2. m_bTerminateThread = TRUE;
  3. ::WaitForSingleObject(m_hThreadB, INFINITE); // blocage potientiel ici !!


 
les threads B sont crées depuis le thread A ou le thread "principal" ?

Reply

Marsh Posté le 02-07-2006 à 21:17:54    

e ne suis pas du tout spécialiste de ce genre de développement, simplement en regardant la doc, j'ai vu cette possibilité justement pour résoudre le problème du "message deadlocks".
Maintenant, si tes collègues ont refusé cette solution, je me vois mal te la conseiller ne connaissant pas les tenants et aboutissants du problème que tu exposes.

Reply

Marsh Posté le 03-07-2006 à 12:19:46    

Salut,
Et si B envoyait un sendmessage a A avec un message perso lui disant qu'il a compris qu'il doit se terminer et que c'est son dernier message?
A comprendrait alors qu'il n'aurait plus de message de B et pourrait passer en WaitForsingleObject sur le thread de B?

Reply

Marsh Posté le 08-07-2006 à 20:40:43    

bjone a écrit :

tu as donc quoi, un .cpp pour la classe de l'application, des .cpp avec les classes des fenêtres, dans l'un de tout ça ou un .cpp a part, tu as tes routines de threads ?


Oui à cela il faut ajouter que l'IHM est exécutée dans son propre thread et que les classes collaboratrices sont des DLL MFC qui s'exécutent dans des threads auxilaires distincts. J'appellerai ces dernières les classes ouvrières. Les classes ouvrières communiquent des notifications à l'IHM sous la forme de messages windows spécifiques.
 
Intérêt de la méthode : les classes ouvrières peuvent être utilisées sans l'IHM (qui n'est qu'un frontal) ET les notifications sont traitées par le thread de l'IHM (ce que le pattern Observer n'aurait pas permis par exemple). Ce détail est très important car le code de l'architecture MFC est très capricieux lorsqu'on utilise le multithread (problème de CWnd / HWND).


Message édité par slash33 le 08-07-2006 à 20:48:50
Reply

Marsh Posté le 08-07-2006 à 20:43:45    

breizhbugs a écrit :

Salut,
Et si B envoyait un sendmessage a A avec un message perso lui disant qu'il a compris qu'il doit se terminer et que c'est son dernier message?
A comprendrait alors qu'il n'aurait plus de message de B et pourrait passer en WaitForsingleObject sur le thread de B?


Non car A est responsable du cycle de vie de B. B ne sait pas quand il doit s'arrêter. D'ailleurs en pratique, le thread B exécute perpétuellement le même scénario juqu'à ce que A lui demande de s'arrêter.

Message cité 1 fois
Message édité par slash33 le 08-07-2006 à 20:44:24
Reply

Marsh Posté le 08-07-2006 à 20:47:18    

bjone a écrit :

il se passe bien quelque chose entre les deux non ?


Oui : l'IHM exécute les requêtes de l'utilisateur et traite les notifications des thread B (ils sont plusieurs en nombre indéfini)
 

bjone a écrit :

les threads B sont crées depuis le thread A ou le thread "principal" ?


Le thread A est le thread principal (de l'IHM donc). Les thread B sont des threads ouvriers qui peuvent communiquer avec l'IHM.
 
En fait, les thread B sont créés à l'initialisation de l'IHM (c'est pas tout à fait vrai mais on s'en fout ici) et sont libérés  à la fermeture de l'IHM.
 
Mes collègues ont trouvé une solution : ils tuent le processus et tous ces threads!!! :ouch: A noter que les thread B pilotent généralement des systèmes électronique actifs. :whistle:


Message édité par slash33 le 08-07-2006 à 20:53:09
Reply

Marsh Posté le 09-07-2006 à 11:53:01    

slash33 a écrit :

Non car A est responsable du cycle de vie de B. B ne sait pas quand il doit s'arrêter. D'ailleurs en pratique, le thread B exécute perpétuellement le même scénario juqu'à ce que A lui demande de s'arrêter.


Ben si:
B sait qu'il faut s'arreter quand A mets sa variable m_bTerminateThread a TRUE.
Dans ce cas, le code de B devient un truc du genre:

Code :
  1. ...
  2. while (!(*pTerminateThread))
  3. {
  4.      if (anEventOccured())
  5.      {
  6.           SendMessage(hWndTarget, WM_THE_EVENT, ...); // si le thread auquel appartient hWndTarget est en attente, je suis mort !
  7.      }
  8.      ::Sleep(50); // dors un peu ; pas besoin de stresser la machine
  9. }
  10. SendMessage(hWndTarget, WM_FINDEB, ...); // Avec en plus le handle de B au cas ou ils y a plusieurs thread B
  11. ...
  12. //Fin de B
  13. ...


 
Quand a A, la gestion des messages est un peu plus compliquées mais pas tant que ca:
1- Dans le gestionnaire du bouton "quitter", vous mettez m_bTerminateThread a TRUE  
puis vous lancer un timer "TCLOSE" qui doit boucler et c'est tout! (c'est a dire que A continu de traiter les messages!)
2- Dans le gestionnaire du message WM_FINDEB, vous recuperer le handle des B qui se ferme. Vous compter le nombre de B qui se ferme (vous enregistrer les handles de B dans un tableau)(On sait combien il y a de threads B puisque tu dis que c'est A qui les crees).
3- Dans le gestionnaire du timer "TCLOSE" (qui est appelé regulierement), vous attendez que tous les threads B soit fermer en utilisant le compteur du point 2. quand ils sont tous fermer, vous pouver a ce moment la faire un WaitForSingleObject sur les handle de B, qui sera passant puisque B ne peut plus envoyé de message apres celui ci par construction!). Lorsque tous les WaitForSingleObject seront passé, vous pouver fermer A!

Message cité 1 fois
Message édité par breizhbugs le 09-07-2006 à 11:56:30
Reply

Marsh Posté le 09-07-2006 à 12:48:53    

breizhbugs a écrit :


Quand a A, la gestion des messages est un peu plus compliquées mais pas tant que ca:
1- Dans le gestionnaire du bouton "quitter", vous mettez m_bTerminateThread a TRUE  
puis vous lancer un timer "TCLOSE" qui doit boucler et c'est tout! (c'est a dire que A continu de traiter les messages!)
2- Dans le gestionnaire du message WM_FINDEB, vous recuperer le handle des B qui se ferme. Vous compter le nombre de B qui se ferme (vous enregistrer les handles de B dans un tableau)(On sait combien il y a de threads B puisque tu dis que c'est A qui les crees).
3- Dans le gestionnaire du timer "TCLOSE" (qui est appelé regulierement), vous attendez que tous les threads B soit fermer en utilisant le compteur du point 2. quand ils sont tous fermer, vous pouver a ce moment la faire un WaitForSingleObject sur les handle de B, qui sera passant puisque B ne peut plus envoyé de message apres celui ci par construction!). Lorsque tous les WaitForSingleObject seront passé, vous pouver fermer A!


Intéressant.
 
Cependant j'ai quelques questions et remarques:
- les threads B ne sont pas détruits uniquement à la fermeture de l'application, ils sont détruits et créés dynamiquement par l'utilisateur. Cependant le modèle doit être exploitable.
- que fait on si l'un des threads B ne se termine pas ?
- est il nécessaire de faire un WaitForSingleObject dans la phase 3


Message édité par slash33 le 09-07-2006 à 12:50:31
Reply

Marsh Posté le 09-07-2006 à 20:09:40    

Citation :

Intéressant.
 
Cependant j'ai quelques questions et remarques:
1- les threads B ne sont pas détruits uniquement à la fermeture de l'application, ils sont détruits et créés dynamiquement par l'utilisateur. Cependant le modèle doit être exploitable.
2- que fait on si l'un des threads B ne se termine pas ?
3- est il nécessaire de faire un WaitForSingleObject dans la phase 3


Re,
1- pour le 1, peu importe comment sont detruit les threads B car quand ils quittent la boucle while, ils envoient
un sendMessage(WM_FINDEB) a A ce qui lui permet de toujours savoir quels sont les threads B qui passe de l'etat "en vie" a "je termine"et donc A peu mettre a jour sa liste de B
 
2- A utilise le timer "TCLOSE", il suffit de controler si ce timer est appelée trop de fois ce qui veut dire
(aaahh but de Zidane sur Penalty a 6 minutes du debut du match :-) qu'un thread B bloque, dans ce cas on kill le thread B je suppose?
 
3- Ma foi j'en sais rien ,ca depend de votre programme mais je suppose que c'est plus propre.

Reply

Marsh Posté le 14-07-2006 à 11:45:28    

Merci pour la solution. On en discute en ce moment mais vu le monde qu'il y a pendant les vacances, le dossier ne va pas avancer bien vite. :o


Message édité par slash33 le 14-07-2006 à 11:45:53
Reply

Marsh Posté le 14-07-2006 à 14:20:44    

:hello:  
Bossez bien alors et donne des nouvelles, histoire de voir si tes collegues apprécient ma suggestion.

Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed