19 votes

Mise en œuvre d'un paquet de threads au niveau de l'utilisateur

J'ai été chargé dans une classe de créer une bibliothèque de threads au niveau de l'utilisateur en C. Je me demandais si quelqu'un pouvait me donner une liste de choses à lire pour accomplir ceci. J'ai une bonne idée de par où commencer, mais toute ressource sur les threads de niveau utilisateur et certains aspects applicables du langage C qui pourraient m'aider serait extrêmement précieuse.

Je ne vois pas très bien comment je pourrais mettre en œuvre un planificateur à cet effet. Supposons que j'ai une assez bonne compréhension du langage C et de certaines de ses fonctions de bibliothèque les plus utiles.

10voto

J. C. Salomon Points 1542

Je l'ai fait pour un devoir à la maison sans écrire le moindre assembleur. Le mécanisme de changement de fil était setjmp / longjmp . Il s'agissait d'allouer de la mémoire pour la pile de chaque thread, puis de manipuler très soigneusement les valeurs dans le fichier jmp_buff pour que l'exécution saute à la pile du prochain thread.

Voir aussi l'article très lisible de Russ Cox. libtask .

Editer en réponse au commentaire de l'OP : Pour décider quand changer de fil, il y a deux directions principales : préemptive et coopérative. Dans le modèle préemptif, vous aurez quelque chose comme un signal de minuterie qui provoque le flux d'exécution de sauter à un thread répartiteur central, qui choisit le prochain thread à exécuter. Dans un modèle coopératif, les threads se "cèdent" les uns aux autres, soit explicitement ( par exemple en appelant un yield() que vous fournirez) ou implicitement ( par exemple , demandant un verrou détenu par un autre thread).

Jetez un coup d'œil à l'API de libtask pour un exemple du modèle coopératif, en particulier la description de la fonction taskyield() . C'est le rendement explicite que j'ai mentionné. Il y a aussi les fonctions d'E/S non bloquantes qui incluent un rendement implicite - la "tâche" actuelle est mise en attente jusqu'à ce que l'E/S soit terminée, mais les autres tâches ont leur chance de s'exécuter.

4voto

mux Points 9745

Un simple planificateur coopératif peut être réalisé en C en utilisant swapcontext, regardez l'exemple dans la page de manuel swapcontext aquí voici son résultat :

$ ./a.out
main: swapcontext(&uctx_main, &uctx_func2)
func2: started
func2: swapcontext(&uctx_func2, &uctx_func1)
func1: started
func1: swapcontext(&uctx_func1, &uctx_func2)
func2: returning
func1: returning
main: exiting

Comme vous pouvez le constater, c'est tout à fait faisable.

Note : si vous permutez le contexte à l'intérieur d'un gestionnaire de signal de timer, alors vous avez vous-même un planificateur préemptif, mais je ne suis pas sûr que ce soit sûr ou possible de le faire.

Edit : J'ai trouvé ceci dans la page de manuel de sigaction qui suggère qu'il est possible de changer le contexte dans un gestionnaire de signal :

Si SA_SIGINFO est spécifié dans sa_flags, alors sa_sigaction (au lieu de sa_handler) spécifie la fonction de traitement du signal pour signum. Cette fonction reçoit le numéro du signal en tant que premier argument, un pointeur vers un siginfo_t comme deuxième argument, un a ucontext_t (cast to void *) comme troisième argument.

2voto

zneak Points 45458

Vous pouvez consulter l'implémentation open-source d'Apple. Notez que la plus grande partie du code est en fait du code assembleur, parce qu'il nécessite certaines choses spécialisées que vous ne pouvez pas faire en C, comme récupérer l'adresse de retour de la trame de pile ou sauter à une adresse arbitraire.

Les threads de l'utilisateur (également appelés "fibres") utilisent généralement un modèle coopératif, c'est-à-dire que les threads s'exécutent jusqu'à ce qu'ils décident qu'ils ont eu assez de temps, puis passent à un autre thread. À l'aide d'une file d'attente prioritaire, vous pouvez mettre en œuvre un planificateur qui exécute la tâche qui a été exécutée le moins longtemps possible. (Le planificateur garde une trace des tâches en cours d'exécution, et la tâche en cours d'exécution cède sa place lorsqu'elle décide qu'elle a eu assez de temps. Le planificateur met à jour le temps d'exécution de la tâche, puis cède à celle qui a eu le moins de temps d'exécution).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X