9
votes

Comment synchroniser le gestionnaire / travailleur Pthreads sans rejoindre?

Je connais avec Multhreading et j'ai développé de nombreux programmes multithreads dans Java et Objective-C avec succès. Mais je ne pouvais pas atteindre ce qui suit en C en utilisant Pthreads sans utiliser de jointure à partir du fil principal:

Join average : 0.153074
Mutex average: 0.071588
Mutex is 53% faster.


6 commentaires

Désolé, n'ai pas lu votre question avec soin. Vous devez implémenter une barrière de réentraînement au commentaire numéroté 2. PCBEC3.IHEP. Su / ~ MIAGKOV / code / barrière.c


@nhahtdh ne vous inquiétez pas pour ça. Je vais regarder le code. Merci!


Michael Burr a suggéré une fonction existante pour le faire. (Je pensais que Posix ne le définissait pas, mais c'est là tout).


Votre calcul final du pourcentage à imprimer est très Dodgy. Si join_avg est 11 et mutex_avg est 10, il signalera que Mutex est de 110% plus rapide, il est effectivement de 110% aussi vite. Je pense que quelque part vous vouliez calculer une différence et réellement calculé un ratio :-)


@Stevejessop Oups! Je vais modifier cela maintenant. Merci!


Dans ce code, vous obtiendrez une erreur pour ceci: int * int ts = malloc (numofint * taille de (int)); . L'erreur est ne peut pas initialiser une variable de type INT * avec une rvalue de type VOID * , vous pouvez résoudre ceci en ajoutant (int *) à malloc int * intens = (int *) mallococ (Numérots * Tailleof (int));


4 Réponses :


1
votes

Pour dire à tous les threads de commencer à travailler, il peut être aussi simple qu'une variable d'entier globale qui est initialisée à zéro et que les threads attendent simplement que ce soit sans zéro. De cette façon, vous n'avez pas besoin du pendant (1) en boucle dans la fonction de fil.

Pour attendre jusqu'à ce qu'ils soient tous terminés, pthread_join est le plus simple car il va réellement bloquer jusqu'à ce que le fil de jonction soit effectué. Il est également nécessaire de nettoyer les trucs du système après le thread (comme sinon la valeur de retour du thread sera stocké pour le reste du programme). Comme vous avez un tableau de tous pthread_t pour les threads, juste en boucle sur eux un par un. Comme cette partie de votre programme ne fait rien d'autre, et doit attendre que tous les threads soient terminés, il suffit d'attendre qu'ils soient corrects.


3 commentaires

Merci Joachim, même question pour Michael: Le fil principal peut-il rejoindre un filetage d'arrière-plan même si le fil d'arrière-plan n'a pas retourné ni appeler pthread_exit ? Parce que comme je l'ai écrit, les threads de fond seront dans une boucle infinie.


@Mota tandis que les fils peuvent être en boucle pendant que le programme est en marche, ils doivent s'arrêter de temps en temps. Soit par vous leur dire de s'arrêter (à l'aide d'une variable entière simple suffit ici aussi) ou en sortant du fil principal (c'est-à-dire de revenir à partir de principal ou appelant sortie ). La fonction pthread_join sera bloquer jusqu'à ce que le fil de jonction est de joindre des sorties.


Merci Joachim! Vos conseils étaient très précieux.



4
votes

Il existe plusieurs mécanismes de synchronisation que vous pouvez utiliser (variables de condition, par exemple). Je pense que le plus simple serait d'utiliser un pthread_barrier pour synchroniser le début des threads.

supposant que vous souhaitez que tous les threads soient "synchronisés" sur chaque itération de la boucle, vous pouvez simplement réutiliser la barrière. Si vous avez besoin de quelque chose de plus flexible, une variable de condition pourrait être plus appropriée.

Lorsque vous décidez qu'il est temps de terminer le fil de monter (vous n'avez pas indiqué comment les threads sauront sortir de la boucle infinie - une simple variable partagée peut être utilisée pour cela; la variable partagée pourrait être un atomique Tapez ou protégé avec un mutex), le fil principal () doit utiliser pthread_join () pour attendre que tous les fils se terminent.


4 commentaires

Merci michael, mais le fil principal peut-il rejoindre un fil d'arrière-plan même si le fil d'arrière-plan n'a pas retourné ni appeler pthread_exit ? Parce que comme je l'ai écrit, les threads de fond seront dans une boucle infinie.


@Mota: Désolé, j'ai raté cette partie de la question. J'ai mis à jour la réponse. Si vos besoins sont simples, la barrière peut être réutilisée; Si vos besoins sont plus complexes, vous pouvez utiliser une variable de condition pthread_cond_t (vous pourriez avoir besoin de plus d'un).


En principe, vous ne besoin plus d'une variable de condition, car différentes personnes peuvent tester différentes conditions à différents moments à l'aide de la même paire Mutex / Condvar, et chaque fois que toute condition change vous diffusez le condvert. Pour l'efficacité et la clarté du code, vous pourriez veulent plus d'un.


Merci michael! Vos conseils étaient très précieux.



3
votes

Vous devez utiliser une technique de synchronisation différente que celle de rejoindre , c'est clair.

Malheureusement, vous avez beaucoup d'options. L'une est une "barrière de synchronisation", qui est essentiellement une chose dans laquelle chaque thread qui atteint des blocs d'utilisation jusqu'à ce qu'ils soient tous atteints (vous spécifiez le nombre de threads à l'avance). Regardez pthread_barrier .

Un autre consiste à utiliser une paire de conditions / mutex ( pthread_cond _ * ). Lorsque chaque thread finit, il faut le mutex, incrémente un compte, signale le condvert. Le fil principal attend sur le condvert jusqu'à ce que le nombre atteigne la valeur qu'il attend. Le code ressemble à ceci: xxx

un autre est d'utiliser un sémaphore par fil - lorsque le fil finit, il publie son propre sémaphore et le fil principal attend chaque sémaphore dans Tournez au lieu de rejoindre chaque fil à tour de rôle.

Vous avez également besoin de synchronisation pour redémarrer les threads du travail suivant - cela pourrait être un deuxième objet de synchronisation du même type que le premier, avec des détails modifiés pour le fait que vous avez 1 affiche et n serveurs plutôt que l'inverse. Ou vous pouvez (avec soin) réutiliser le même objet aux deux fins.

Si vous avez essayé ces choses et que votre code n'a pas fonctionné, peut-être poser une nouvelle question spécifique sur le code que vous avez essayé . Tous sont adéquats à la tâche.


1 commentaires

Merci Steve! Vos astuces étaient les plus précieuses :)



2
votes

Vous travaillez au mauvais niveau d'abstraction. Ce problème a déjà été résolu. Vous réintroduisez une file d'attente de travail + piscine de fil.

OpenMP semble être un bon ajustement pour votre problème. Il convertit #pragma annotations en code fileté. Je crois que cela vous permettrait d'exprimer ce que vous essayez de faire assez directement.

en utilisant libdispatch , ce que vous essayez de faire serait exprimé en tant que Dispatch_apply cibler une file d'attente simultanée. Cela attend implicitement que toutes les tâches d'enfant complètent. Sous OS X, il est mis en œuvre à l'aide d'une interface de WorkQueue Pthread non portable; Sous FreeBSD, je crois qu'il gère directement un groupe de Pthreads.

S'il s'agit de préoccupations de portabilité vous conduisant à l'utilisation de Pthreads bruts, n'utilisez pas de barrières Pthread. Les barrières sont une extension supplémentaire sur les threads de base de base et au-dessus de la base. OS X Par exemple ne le supporte pas. Pour plus, voir POSIX .

Blocage du fil principal jusqu'à ce que tous les filets d'enfants soient terminés puisse être effectué à l'aide d'un nombre protégé par une variable de condition ou, encore plus simplement, à l'aide d'un tuyau et d'un blocage de lecture où le nombre d'octets à lire correspond au nombre de filets. Chaque thread écrit un octet sur l'achèvement du travail, puis dort jusqu'à ce qu'il soit de nouveau travail du fil principal. Le fil principal débloque une fois que chaque fil a écrit son "j'ai fini!" octet.

Le travail de passage sur les fils d'enfants peut être effectué à l'aide d'un mutex protégeant le descripteur de travail et une condition pour signaler de nouveaux travaux. Vous pouvez utiliser un seul tableau de descripteurs de travail que tous les threads tirés de. Sur le signal, chacun essaie de saisir le mutex. En saisissant le mutex, il dérogerait un peu de travail, signalerait de nouveau si la file d'attente n'est pas importante, puis traite son travail, après quoi il signalerait la fin du fil principal.

Vous pouvez réutiliser cette "file d'attente de travail" pour débloquer le fil principal en enlevant les résultats, avec le fil principal en attente jusqu'à la longueur de la file d'attente de résultat correspond au nombre de threads; L'approche de tuyau utilise simplement un blocage lire pour ce comptage pour vous.


1 commentaires

Merci Jeremy! Vos conseils étaient très précieux!