Passer plusieurs paramètres à un thread

Partagez votre expérience de PureBasic avec les autres utilisateurs.
lepiaf31
Messages : 510
Inscription : dim. 25/mars/2007 13:44
Localisation : Toulouse, France
Contact :

Passer plusieurs paramètres à un thread

Message par lepiaf31 »

Bonjour à vous !

Bon depuis que je code en purebasic, je suis toujours embêté avec ces fichus threads. A chaque fois qu'on veut passer plusieurs paramètres à un thread, il faut bidouiller une structure et faire hyper attention à ce qu'on fait.

En général, lorsqu'on veut passer plusieurs paramètres à un thread, on conçoit une structure (rien que pour ça bien souvent), et on met dans cette structure tout ce qu'on veut envoyer à notre thread. Alors c'est plutôt une bonne technique sauf que:
1° On est obligé de concevoir une structure rien que pour cela
2° Si on veut rajouter un paramètre, il faut changer la structure
3° On se retrouve souvent avec des "Invalid Memory Access"

Je vais insister sur ce dernier point: l'une des raisons qui fait qu'on a des erreurs d'accès mémoire c'est qu'on passe la structure à notre thread par pointeur. De ce fait, il faut que ce pointeur et toutes les données qu'il contient restent valides durant toute la vie du thread. Et parfois, on oublie ou on copie mal certaines données qui font que ben .. ca plante sans qu'on comprenne pourquoi.
Je donne un exemple:

Code : Tout sélectionner

Structure test
  param1.i
  param2.s
EndStructure

;structure du thread
Structure th
  var1.i
  var2.s
  var.test
EndStructure


Procedure thread(*paramList.th)
  Delay(2000) ;attente de la fin de la fonction appelante
  
  Debug *paramList\var1
  Debug *paramList\var2
  Debug *paramList\var\param1
  Debug *paramList\var\param2
EndProcedure

Procedure callThread()
  var1.i = 1234
  var2.s = "test"
  var.test\param1 = 789
  var\param2 = "Hello World !"
  
  envoi.th
  envoi\var1 = var1
  envoi\var2 = var2
  envoi\var = var
  
  t = CreateThread(@thread(), @envoi)
  ProcedureReturn t
EndProcedure

WaitThread(callThread())
Théoriquement, vous devriez voir des variables erronées s'afficher (si ce n'est un plantage). Dans ce code, tout le problème est que j'appelle le thread via une procédure. Or à la fin de la procédure, toutes les variables créées par celle-ci sont tout simplement supprimées (elles sont locales à la procédure). Du coup, notre thread se retrouve avec un pointeur sur une zone mémoire qui n'est plus allouée.

Pour palier à ce problème j'ai donc fait quelques fonctions qui permettent de nous simplifier la vie. Ces procédures permettent d'ajouter simplement des paramètres à une unique ressource. C'est ensuite cette ressources qui est passée au thread et d'autres procédures permettent de récupérer les différents paramètres de la ressource:

Code : Tout sélectionner

; **************************************************
; Gestionnaire de passage de parametres à un Thread.
; Par SERIN Kévin
; PB 4.51
; **************************************************

;Structure contenant la liste des adresses des parametres.
Structure PThread
  List param.i()
EndStructure

;Instancie une liste de paramètres.
;   return: une ressource de type PThread.
Procedure pThread_create()
  *pThread.PThread = AllocateMemory(SizeOf(PThread))
  NewList *pThread\param()
  
  ProcedureReturn *pThread
EndProcedure

;Fait une copie de la donnée et l'ajoute à la liste des paramètres. 
;Attention: pour les structures, utilisez la macro pThread_addStruct().
;   pThread:  ressource PThread (créée avec pThread_create() )
;   value:    adresse de la donnée à ajouter
;   size:     taille de la donnée à ajouter
Procedure pThread_addValue(*pThread.PThread, *value, size.i)
  *newElement = AllocateMemory(size)
  CopyMemory(*value, *newElement, size)
  
  AddElement(*pThread\param())
  *pThread\param() = *newElement
EndProcedure

;Ajoute directement une adresse à la liste des paramètres.
;Attention: aucune copie n'est effectuée, la zone mémoire doit donc restée accessible à tout moment. Préférez pThread_addValue().
;   pThread:  ressource PThread (créée avec pThread_create() ).
;   adrs:     adresse à ajouter.
Procedure pThread_addAddress(*pThread.PThread, *adrs)
  AddElement(*pThread\param())
  *pThread\param() = *adrs
EndProcedure

;Récupère l'adresse du ième paramètre (commence à 1).
;   pThread:  ressource PThread (créée avec pThread_create() ).
;   i:        index du paramètre.
Procedure pThread_get(*pThread.PThread, i.i)
  SelectElement(*pThread\param(), i-1)
  ProcedureReturn *pThread\param()
EndProcedure

;Libère une ressource PThread ainsi que tous les paramètres
;   pThread:  ressource PThread (créée avec pThread_create() ).
Procedure pThread_free(*pThread.PThread)
  ForEach *pThread\param()
    FreeMemory(*pThread\param())
  Next
  ClearList(*pThread\param())
  FreeMemory(*pThread)
EndProcedure

;Macro permettant d'ajouter une structure à la liste des paramètres.
;   pThread:  ressource PThread (créée avec pThread_create() ).
;   varAdrs:  adresse de la variable contenant la structure à ajouter.
;   tempVar:  nom de variable temporaire (poubelle). Ce nom ne doit pas être utilisé par une autre variable de votre code.
;   struct:   nom de la structure de la variable.
Macro pThread_addStruct(pThread, varAdrs, tempVar, struct)
  tempVar = AllocateMemory(SizeOf(struct))
  CopyStructure(varAdrs, tempVar, struct)
  pThread_addAddress(pThread, tempVar)
EndMacro
Et voilà ce que ca donne avec l'exemple de tout à l'heure:

Code : Tout sélectionner

; ********
; Exemple:
; ********

Structure test
  param1.i
  param2.s
EndStructure

Procedure thread(paramList)
  Delay(2000) ;attente de la fin de la fonction appelante
  
  var1.i = PeekI(pThread_get(paramList, 1)) ;récupération du 1er parametre
  var2.s = PeekS(pThread_get(paramList, 2)) ;récupération du 2e parametre
  *varStruct.test = pThread_get(paramList, 3) ;récupération du 3e parametre
  
  Debug var1
  Debug var2
  Debug *varStruct\param1
  Debug *varStruct\param2
  
  pThread_free(paramList) ;liberation des ressources à la fin du thread
EndProcedure

Procedure callThread()
  var1.i = 1234
  var2.s = "test"
  var.test\param1 = 789
  var\param2 = "Hello World !"
  
  param = pThread_create()
  pThread_addValue(param, @var1, SizeOf(Integer))
  pThread_addValue(param, @var2, Len(var2)+1)
  pThread_addStruct(param, @var, tmp, test)
  t = CreateThread(@thread(), param)
  ProcedureReturn t
EndProcedure

WaitThread(callThread())
Là tout se passe bien, aucune erreur. =)

Voilà en espérant que cela vous serve à quelque chose.
dayvid
Messages : 1242
Inscription : mer. 11/nov./2009 18:17
Localisation : Poitiers (Vienne)

Re: Passer plusieurs paramètres à un thread

Message par dayvid »

C'est vrais que c'est énèrvent sa !

Pour moi c'est du chinoi :wink:
La vie, C'est comme, Une boitte, De startis, On en voie, De toutes, Les couleurs !

Mon forum http://purebasic.forumphp3.com/index.php
Mesa
Messages : 1126
Inscription : mer. 14/sept./2011 16:59

Re: Passer plusieurs paramètres à un thread

Message par Mesa »

Une autre façon est de définir des variables globales qui ont donc une durée de vie indépendante des threads.

Chez moi ça marche (avec option du compilateur à "activer la gestion des threads" même si bizarrement ça marche sans ?!))

J'ai juste ajouté 2 variables globales en lignes 13 et 14.

Code : Tout sélectionner

Structure test
  param1.i
  param2.s
EndStructure

;structure du thread
Structure th
  var1.i
  var2.s
  var.test
EndStructure

Global var.test
Global envoi.th

Procedure thread(*paramList.th)
  Delay(2000) ;attente de la fin de la fonction appelante
 
  Debug *paramList\var1
  Debug *paramList\var2
  Debug *paramList\var\param1
  Debug *paramList\var\param2
EndProcedure

Procedure callThread()
  var1.i = 1234
  var2.s = "test"
  var.test\param1 = 789
  var\param2 = "Hello World !"
 
  ;envoi.th
  envoi\var1 = var1
  envoi\var2 = var2
  envoi\var = var
 
  t = CreateThread(@thread(), @envoi)
  ProcedureReturn t
EndProcedure

WaitThread(callThread())
Mesa.
lepiaf31
Messages : 510
Inscription : dim. 25/mars/2007 13:44
Localisation : Toulouse, France
Contact :

Re: Passer plusieurs paramètres à un thread

Message par lepiaf31 »

Eh bien je vais peut-être passer pour un puriste mais je suis complètement anti-globales ^^ On les oublie souvent et on peut parfois les écraser sans s'en rendre compte (surtout sur les gros projets).
De plus, avec cette méthode on ne peut pas appeler plusieurs thread de la même procédure avec des paramètres différents(et je trouve ca un peu bête quand même ^^ ).
Mesa
Messages : 1126
Inscription : mer. 14/sept./2011 16:59

Re: Passer plusieurs paramètres à un thread

Message par Mesa »

Pour ma part, être anti-global est nécessaire dans le contexte de l'encapsulation de la programmation orientée objet (poo) sinon je trouve plus facile de créer un pbi nommé "global" dont toutes les variables globales commence par... "GLOBALE_". Et là il est impossible d'oublier ou de confondre quoique ce soit.

ça revient au même mais ça évite de créer des variables temporaires et de faire des échanges permanents entre elles.

Mais bon, chacun fait comme il lui plaît...

Existent-t-ils de gros projets sans poo ? en Purebasic ?
Je ne sais pas, peut-être.

J'ai essayé de faire ça avec ton code
De plus, avec cette méthode on ne peut pas appeler plusieurs thread de la même procédure avec des paramètres différents(et je trouve ca un peu bête quand même ^^ ).

et je n'y suis pas arrivé. Peux-tu me montrer stp ? ça m'intéresse.

Mesa.
lepiaf31
Messages : 510
Inscription : dim. 25/mars/2007 13:44
Localisation : Toulouse, France
Contact :

Re: Passer plusieurs paramètres à un thread

Message par lepiaf31 »

Code : Tout sélectionner

IncludeFile "pThread.pbi"

; ********
; Exemple:
; ********

Structure test
  param1.i
  param2.s
EndStructure

Procedure thread(paramList)
  Delay(2000) ;attente de la fin de la fonction appelante
  
  var1.i = PeekI(pThread_get(paramList, 1))
  var2.s = PeekS(pThread_get(paramList, 2))
  *varStruct.test = pThread_get(paramList, 3)
  
  Debug var1
  Debug var2
  Debug *varStruct\param1
  Debug *varStruct\param2
  
  pThread_free(paramList) ;liberation des ressources à la fin du thread
EndProcedure

Procedure callThread()
  var1.i = 1234
  var2.s = "test"
  var.test\param1 = 789
  var\param2 = "Hello World !"
  
  param = pThread_create()
  pThread_addValue(param, @var1, SizeOf(Integer))
  pThread_addValue(param, @var2, Len(var2)+1)
  pThread_addStruct(param, @var, tmp, test)
  
  
  ;2e thread
  var1.i = 50
  var2.s = "test deux le retour"
  var.test\param1 = 12
  var\param2 = "Hello World ! (deux)"
  param2 = pThread_create()
  pThread_addValue(param2, @var1, SizeOf(Integer))
  pThread_addValue(param2, @var2, Len(var2)+1)
  pThread_addStruct(param2, @var, tmp, test)
  
  ;envoi des 2 threads en meme temps
  t = CreateThread(@thread(), param)
  t = CreateThread(@thread(), param2)
  ProcedureReturn t
EndProcedure

callThread()
Delay(5000) ;attente des threads
Bon j'ai mis les appels des 2 threads dans la même fonction mais c'est pour montrer le principe. C'est plus clair comme ca ?
Mesa
Messages : 1126
Inscription : mer. 14/sept./2011 16:59

Re: Passer plusieurs paramètres à un thread

Message par Mesa »

Excellent, merci.
lepiaf31
Messages : 510
Inscription : dim. 25/mars/2007 13:44
Localisation : Toulouse, France
Contact :

Re: Passer plusieurs paramètres à un thread

Message par lepiaf31 »

Avec plaisir =)
En espérant que ca soit utile à d'autres =)
Avatar de l’utilisateur
graph100
Messages : 1318
Inscription : sam. 21/mai/2005 17:50

Re: Passer plusieurs paramètres à un thread

Message par graph100 »

C'est vraiment sympa ce que tu as fait, simplement dans le cas d'une manipulation de donnée qui doit être rapide, il me semble que ça alourdi un peu les choses.

de mon coté Je vois les threads comme une famille de procédure, donc créer une structure associée à chaque thread n'est pas un soucis, il me semble, de cette manière comme on peut maintenant mettre tout les objets PB dans une structure (liste, array, map), on peut avoir accès aux données transmises simplement et de manière pratique avec l'autocomplétion (pendant le codage)

ensuite c'est vrai que lors du lancement d'un thread par procédure il faut absolument faire attention à ce que les données aient une durée de vie plus grande que celle du thread.

Du coup pour ne pas avoir ce problème, utiliser une var globale avec en structure toutes les données du prog me semble être plus "pratique" dans ce que ça t'oblige à maitriser les données que tu stockes.
_________________________________________________
Mon site : CeriseCode (Attention Chantier perpétuel ;))
lepiaf31
Messages : 510
Inscription : dim. 25/mars/2007 13:44
Localisation : Toulouse, France
Contact :

Re: Passer plusieurs paramètres à un thread

Message par lepiaf31 »

graph100 a écrit :C'est vraiment sympa ce que tu as fait, simplement dans le cas d'une manipulation de donnée qui doit être rapide, il me semble que ça alourdi un peu les choses.
Certes, il n'y a pas de doute là-dessus. Mais selon moi, ce qui doit être "rapide" c'est l’exécution du thread en lui même et pas tellement son initialisation. Je pense qu'il vaut mieux optimiser ses algorithmes plutôt que de supprimer ces 2-3 accès mémoire en plus ;).
D'autant plus que le soucis avec les threads c'est bien souvent qu'on a l'impression que ca marche (parce qu'on a de la chance) et un bon jour pouf, le programme plante sans vraiment qu'on comprenne pourquoi.

Enfin après chacun doit s'adapter à la situation mais je pense que dans la majeure partie des cas, c'est rassurant d'avoir un thread avec un zone mémoire bien définie et personnelle afin d'avoir un programme stable.
Répondre