Je vais implémenter ma DLL et je fais faire plusieurs tests.
Je vais implémenter ma DLL et je fais faire plusieurs tests.
Bon au niveau des timing, rien à dire quand je veux un pulse de 20ms j'ai exactement 20ms (voire 21ms). On peut difficilement faire plus précis
Par contre, je ne vois pas de différences majeures entre des threads en priorité basse ou en priorité haute...
De même, j'ai l'impression que créer ou réveiller un thread prend du temps![]()
On peut toujours, mais ça devient un effort parfois disproportionné par rapport au gain obtenu.
Tant que ton PC ne "fout rien", il n'y a pas beaucoup de différence en effet. Un thread en haute priorité (voire en "real time") te permet d'avoir d'autres programmes actifs sans plomber tes timings, ne serait-ce que ton environnement de développement.
En fait, Windows est plutôt efficace sur la création / gestion des threads. Par contre, il y a de fortes chances que tu te heurtes au problème du quantum de temps : lorsque tu crées un thread, il n'est en rien garanti que tu auras une commutation de contexte vers ce nouveau thread avant l'expiration du quantum de temps du thread père... Idem pour le réveil du thread, d'ailleurs.
Quelques liens en vrac :
- http://msdn.microsoft.com/en-us/library/ms810434.aspx
- http://msdn.microsoft.com/en-us/library/ms810029.aspx
(Chercher "thread quantum" sur MSDN)
Pour pallier ce problème, il faut un peu taper en touche en augmentant au maximum la priorité du thread que tu veux réveiller (ou vers lequel tu veux switcher). Par exemple, dans un tel cas de figure, tu ne mets pas en thread en suspension (SuspendThread / ResumeThread) : tu mets le thread que tu veux endormir en attente d'un mutex, et tu le réveilles en libérant ce mutex. Comme il est en haute priorité, tu es dans le cas "A higher-priority thread becomes ready to run", ce qui est une condition de commutation de contexte. Si ton thread était de même priorité que celui qui l'a réveillé, il faudrait attendre la fin du quantum de temps du thread déclencheur pour basculer vers le thread venant d'être réveillé.
Mac LAK.
___________________________________________________
Ne prenez pas la vie trop au sérieux, de toutes façons, vous n'en sortirez pas vivant.
Sources et composants Delphi sur mon site, L'antre du Lak.
Pas de question technique par MP : posez-la dans un nouveau sujet, sur le forum adéquat.
Rejoignez-nous sur : ► Serveur de fichiers [NAS] ► Le Tableau de bord projets ► Le groupe de travail ICMO
N'oublions pas non plus que sous Windows, un thread qui termine une attente reçoit un "boost" temporaire de priorité. Par contre, j'ignore si ça s'applique à suspend/resume.
De toute façon, suspend n'est pas "safe": La seule combinaison "safe" est CREATE_SUSPENDED/resume.
SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.
"Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
Apparently everyone. -- Raymond Chen.
Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.
Ce n'est pas le cas. Après un resume, le thread est simplement réinséré dans le scheduling normal : il ne peut donc pas être activé avant la fin du quantum de temps courant, ou une condition de commutation de contexte... De plus, il peut également se faire piquer la place par quelqu'un d'autre.
Effectivement, et je n'avais pas insisté sur ce point : lorsqu'un thread libère un objet de synchronisation et qu'un thread PLUS prioritaire était en attente dessus, il est quasi-certain que le contexte va basculer immédiatement vers le thread venant d'être libéré (hors thread encore plus prioritaire et/ou kernel, bien entendu). Ce n'est pas vrai si les deux threads ont la même priorité, ou que le thread libéré possède une priorité plus basse.
Si tu joues un peu avec les affinités processeur, tu peux presque à coup sûr assurer l'activation du thread haute priorité, notamment en le laissant tout seul sur un cœur : s'il est en real time, il y a de très fortes chances qu'il soit également le seul et unique thread avec une telle priorité sur ce cœur, donc très fortes chances qu'il grille la politesse à tout le monde et s'active immédiatement.
Disons que ça marche pas mal si tu as un thread non critique, ne verrouillant rien pendant qu'il est endormi et avec un temps de réactivité dont on se contrefiche... Et, surtout, que c'est le thread à "endormir" qui appelle lui-même SuspendThread !
Parce qu'endormir un thread autre que soi-même, c'est au mieux courageux... Au pire, c'est stupide, car source d'interblocages sévères.
Mac LAK.
___________________________________________________
Ne prenez pas la vie trop au sérieux, de toutes façons, vous n'en sortirez pas vivant.
Sources et composants Delphi sur mon site, L'antre du Lak.
Pas de question technique par MP : posez-la dans un nouveau sujet, sur le forum adéquat.
Rejoignez-nous sur : ► Serveur de fichiers [NAS] ► Le Tableau de bord projets ► Le groupe de travail ICMO
Pour créer le thread, je crois qu'effectivement c'est assez rapide mais j'ai l'impression que c'est la routine associée au thread qui ne démarre pas tout de suite... C'est ce que tu voulais dire quand tu parles de quantum de temps ?
Envoyé par Mac LAK
Ce point là m'ennuie beaucoup :
en effet le programme qui appelle la DLL (et elle même qui envoie le pulse) écrit dans un fichier log le "timestamp" auquel a eu lieu l'événement. Le gros problème est qu'entre le moment où l'événement est écrit dans le fichier log et l'envoi réel du pulse il s'écoule un temps assez long(trop long en ce qui me concerne)
Dans un premier temps, mon programme principal était organisé de cette manière :
Code X : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 1- log de l'événement 2- appel DLL ( création du thread + appel de la fonction threadée + envoi du pulse)
Ensuite, pour réduire les écarts de temps, j'ai placé les instructions dans un ordre précis (j'ai inversé les instructions tout simplement) :
Je me suis dit que dans cet ordre, je n'allais pas tenir compte du temps nécessaire à la création du thread (ce qui m'intéresse est la date à laquelle le pulse est envoyé). Mais rien n'y fait
Code X : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 1- appel DLL ( création du thread + appel de la fonction threadée + envoi du pulse) 2- log de l'événement
A moins de placer un flag dans la fonction threadée et d'attendre que ce flag passe à 1 avant de loguer l'événement ?
Ça complique un peu plus les choses non ?En plus ça fait une attente active dans le programme appelant.....
Je crois que je vais quand même essayer ne serait-ce que pour voir si l'attente en question est longue. Si c'est assez court (< 5ms on va dire) ça peut se jouer.
Encore un casse-tête![]()
Tout à fait, c'est bien ça. La création "réelle" d'un thread Windows est très rapide : en gros, c'est une prise de mutex kernel, une copie de structure et relâchement du mutex.
Par contre, rien n'est garanti en ce qui concerne le démarrage lui-même du thread : c'est pour cela qu'en règle générale, on crée un pool de threads en début de programme que l'on bloque sur des sémaphores.
Ainsi, de par les conditions de commutation de contexte, on peut basculer vers ces threads de façon quasi-instantanée et surtout sans se fader le quantum de temps de l'appelant.
Pour ça, il te faut soit threader ton log, soit (mieux) horodater une FIFO interne de log, qui sera écrite sur le disque par un thread basse priorité. Les données sont poussées dans la FIFO par le thread générateur, après être sorti de son attente de sémaphore (déblocage sur consigne de génération) et après avoir généré le pulse.
Yep. Il te faut mémoriser le timestamp (opération rapide normalement), générer le pulse, faire le log (le thread de log se débrouille ensuite tout seul), et de nouveau basculer en attente bloquante d'un sémaphore.
Voilà un petit schéma résumant un peu le séquencement :
Est-ce un peu plus clair comme ça ? Bien sûr, la création des threads se fait lors du chargement de la DLL, via une fonction d'initialisation. N'essaie pas de le faire dans le DllMain, tu t'exposerais à des effets de bord indésirables.
Mac LAK.
___________________________________________________
Ne prenez pas la vie trop au sérieux, de toutes façons, vous n'en sortirez pas vivant.
Sources et composants Delphi sur mon site, L'antre du Lak.
Pas de question technique par MP : posez-la dans un nouveau sujet, sur le forum adéquat.
Rejoignez-nous sur : ► Serveur de fichiers [NAS] ► Le Tableau de bord projets ► Le groupe de travail ICMO
Le langage du programme principal ne sait pas faire les threads, ensuite threader le log est impossible car je ne peux pas gérer le log en dehors du programme principal.
C'est pour ça que je passe par une DLL pour l'envoi des pulse. Le but du jeu est donc de synchroniser tout ça
Bon je sens que ça ne va pas être aussi simple que ça.
Ah oui une question pratique pendant que j'y pense :
si j'arrête mon programme sans décharger la DLL que se passe-t-il pour la DLL et les threads ?
Est-ce que la DLL est déchargée automatiquement ?
Les threads créés (et en attente d'un start) sont-ils détruits ? Ou restent-il en mémoire ?
Ça dépend ce que tu appelles "arrêter le programme".
En théorie, un processus s'arrête naturellement quand tous ses threads sont arrêtés. En pratique, ça n'arrive plus qu'en Java (et encore), pour des raisons variées à commencer par les threads sur lesquels tu n'as aucun contrôle (threads de pool, threads de synchonisation COM, etc.).
De nos jours, il y a un appel automatique à ExitProcess() quand main() retourne. Que se passe-t-il à ce moment-là s'il y a d'autres threads qui tournent? C'est moche. Ils sont tués, directement. À coup de TerminateThread(). Y compris s'ils sont dans une section critique.
Puis les DllMain() des différentes DLL sont appelées. Le problème, c'est qu'à cause du problème des sections critiques, tu ne sais pas ce que tu peux faire sans danger (y compris une allocation dans le tas!). À ce moment-là, Raymond Chen conseille de ne toucher à rien qui soit automatiquement détruit à la fin du processus, car c'est plus simple. Par contre, que l'Empereur te protège si tes DLLs jouent avec de la mémoire partagée...
Pour atexit()/onexit() et les destructeurs d'objets globaux, je ne sais plus exactement quand ça joue (ni même si ça joue), mais ça doit être aux alentours de cette étape.
Ensuite, tous les handles vers des objets du Kernel que le processus a d'ouverts sont automatiquement fermés, et les objets correspondants détruits quand c'est approprié. Les DLLs qui ne sont plus utilisées par des processus sont déchargées, les pages mémoire allouées au processus sont libérées.
Et si tu joues avec TerminateProcess() à la place, c'est tout aussi sale, mais les DllMain() ne sont pas appelées, ni les destructeurs et cie.
SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.
"Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
Apparently everyone. -- Raymond Chen.
Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.
L'utilisateur est en mesure de stopper le script à tout moment en appuyant sur une touche. Du coup, je me demandais si la DLL et les threads restaient en mémoire ou si elle était déchargée automatiquement en entrainant la destruction des threads.Envoyé par Médinoc
Ensuite, comme j'arrête le script sans prendre la précaution de détruire les threads ni même décharger la DLL, je me dis que je vais forcément perdre leur handle et je crains d'avoir des fuites de mémoire (des threads inaccessibles) voire même l'impossibilité de communiquer avec ma carte car un thread "perdu" monopolise toujours les ressources.
Mais si, il peut...Tu n'as qu'à appeler une fonction d'initialisation de ta DLL, tout simplement, qui va créer ces threads.
Cela ne change pas du tout le fait que tu seras dans le contexte d'exécution (=le thread) de l'appelant, peu importe à quel endroit et dans quel langage est réalisée l'action.
Tout est tué. La fuite mémoire n'est pas vraiment un risque dans ce cas, SAUF si tu alloues de la mémoire au niveau système (ex : mémoire partagée, allocations globales, mutex nommés, etc.), et encore.
La manière propre d'arrêter est effectivement d'appeler une fonction d'arrêt de la DLL, mais là, tout dépend de ce que tu fais dans le script : l'arrêt est de type "Ctrl+C", c'est à dire "brutal", ou est-ce une boucle proprement stoppée via un test régulier d'une fonction type kbhit() ?
Dans le premier cas, c'est sale. Tout court. Dans le deuxième cas, tu peux toujours explicitement appeler une fonction d'arrêt.
Peu probable. La fin du processus flingue les threads lui appartenant, et tant que tes handles sont locaux aux threads / processus, il seront normalement détruits avec le processus.
Le risque est en général sur les éléments systèmes (= alloués de façon globale), pas sur les éléments locaux.
Mac LAK.
___________________________________________________
Ne prenez pas la vie trop au sérieux, de toutes façons, vous n'en sortirez pas vivant.
Sources et composants Delphi sur mon site, L'antre du Lak.
Pas de question technique par MP : posez-la dans un nouveau sujet, sur le forum adéquat.
Rejoignez-nous sur : ► Serveur de fichiers [NAS] ► Le Tableau de bord projets ► Le groupe de travail ICMO
La solution logicielle n'est pas satisfaisante
Les délais entre la commande et la sortie sont trop longs. Je me suis rabattu sur une solution électronique(générateur de trains d'impulsions).
Arf, dommage... Tu n'arrivais pas à tenir les timings à la milli, ils ont demandé une résolution plus fine, bref quelle raison faisait que ça ne tenait pas ?
Mac LAK.
___________________________________________________
Ne prenez pas la vie trop au sérieux, de toutes façons, vous n'en sortirez pas vivant.
Sources et composants Delphi sur mon site, L'antre du Lak.
Pas de question technique par MP : posez-la dans un nouveau sujet, sur le forum adéquat.
Rejoignez-nous sur : ► Serveur de fichiers [NAS] ► Le Tableau de bord projets ► Le groupe de travail ICMO
La DLL envoyait bien les pulses. Les timing étaient bien respectés.
Mais c'était au niveau du programme appelant la DLL que ça coinçait :
entre le moment où je donne l'ordre d'envoyer un pulse et le moment où le pulse était envoyé, un temps trop long s'écoulait.
Du coup le programme principal envoie 1 pulse (et logue en même temps l'événement) et l'électronique fait le reste![]()
Mac LAK.
___________________________________________________
Ne prenez pas la vie trop au sérieux, de toutes façons, vous n'en sortirez pas vivant.
Sources et composants Delphi sur mon site, L'antre du Lak.
Pas de question technique par MP : posez-la dans un nouveau sujet, sur le forum adéquat.
Rejoignez-nous sur : ► Serveur de fichiers [NAS] ► Le Tableau de bord projets ► Le groupe de travail ICMO
Partager