web-dev-qa-db-fra.com

Comment puis-je attendre que tous les threads soient terminés?

Je veux juste que mon thread principal attende que tous mes (p) threads soient terminés avant de quitter. 

Les fils vont et viennent beaucoup pour différentes raisons, et je ne veux vraiment pas les garder tous, je veux juste savoir quand ils sont tous partis.

wait () le fait pour les processus enfants, renvoyant ECHILD lorsqu'il ne reste plus aucun enfant. Cependant, wait ne semble pas (semble fonctionner avec) (p).

Je ne veux vraiment pas avoir la peine de garder une liste de tous les fils en suspens (comme ils vont et viennent), puis de devoir appeler pthread_join sur chacun d'eux.

Comme il existe un moyen rapide et sale de le faire?

34
Brad

Le bon moyen est de garder une trace de tous vos pthread_id, mais vous avez demandé un moyen rapide et sale alors le voici. Fondamentalement:

  • il suffit de garder un nombre total de threads en cours d'exécution, 
  • incrémentez-le dans la boucle principale avant d'appeler pthread_create, 
  • décrémentez le nombre de threads à la fin de chaque thread. 
  • Puis mettez-vous en veille à la fin du processus principal jusqu'à ce que le décompte revienne à 0.

.

volatile int running_threads = 0;
pthread_mutex_t running_mutex = PTHREAD_MUTEX_INITIALIZER;

void * threadStart()
{
   // do the thread work
   pthread_mutex_lock(&running_mutex);
   running_threads--;
   pthread_mutex_unlock(&running_mutex);
}

int main()
{
  for (i = 0; i < num_threads;i++)
  {
     pthread_mutex_lock(&running_mutex);
     running_threads++;
     pthread_mutex_unlock(&running_mutex);
     // launch thread

  }

  while (running_threads > 0)
  {
     sleep(1);
  }
}
22
gravitron

Voulez-vous que votre thread principal fasse quelque chose de particulier une fois que tous les threads sont terminés?

Sinon, vous pouvez demander à votre thread principal d'appeler simplement pthread_exit() au lieu de revenir (ou d'appeler exit()). 

Si main() le renvoie, il appelle implicitement (ou se comporte comme s'il l'appelait) exit(), ce qui mettra fin au processus. Cependant, si main() appelle pthread_exit() au lieu de retourner, cet appel implicite à exit() ne se produit pas et le processus ne se termine pas immédiatement. Il se terminera lorsque tous les threads seront terminés.

Je ne peux pas avoir trop de quick-n-plus sale.

Voici un petit exemple de programme qui vous permettra de voir la différence. Passez -DUSE_PTHREAD_EXIT au compilateur pour voir le processus attendre que tous les threads soient terminés. Compilez sans définir cette macro pour voir le processus arrêter les threads dans leurs pistes. 

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>

static
void sleep(int ms)
{
    struct timespec waittime;

    waittime.tv_sec = (ms / 1000);
    ms = ms % 1000;
    waittime.tv_nsec = ms * 1000 * 1000;

    nanosleep( &waittime, NULL);
}

void* threadfunc( void* c)
{
    int id = (int) c;
    int i = 0;

    for (i = 0 ; i < 12; ++i) {
        printf( "thread %d, iteration %d\n", id, i);
        sleep(10);
    }

    return 0;
}


int main()
{
    int i = 4;

    for (; i; --i) {
        pthread_t* tcb = malloc( sizeof(*tcb));

        pthread_create( tcb, NULL, threadfunc, (void*) i);
    }

    sleep(40);

#ifdef USE_PTHREAD_EXIT
    pthread_exit(0);
#endif

    return 0;
}
26
Michael Burr

Si vous ne voulez pas garder une trace de vos threads, vous pouvez les détacher pour ne pas vous soucier d'eux, mais pour pouvoir dire quand ils seront terminés, vous devrez aller un peu plus loin.

Une astuce consiste à conserver une liste (liste chaînée, tableau, peu importe) des statuts des threads. Quand un thread commence, il définit son statut dans le tableau à quelque chose comme THREAD_STATUS_RUNNING et juste avant la fin, met à jour son statut à quelque chose comme THREAD_STATUS_STOPPED. Ensuite, lorsque vous souhaitez vérifier si tous les threads se sont arrêtés, vous pouvez simplement parcourir ce tableau et vérifier tous les statuts.

N'oubliez pas cependant que si vous faites quelque chose comme ça, vous aurez besoin de contrôler l'accès au tableau de sorte qu'un seul thread puisse y accéder (lire et write) à la fois. un mutex dessus.

2
SlappyTheFish

vous pouvez garder une liste de tous vos identifiants de threads et ensuite pthread_join sur chacun d'eux. vous aurez également besoin d’une sorte de liste pouvant être modifiée lors de son itération, par exemple, un std :: set <pthread_t>?

int main() {
   pthread_mutex_lock(&mutex);

   void *data;
   for(threadId in threadIdList) {
      pthread_mutex_unlock(&mutex);
      pthread_join(threadId, &data);
      pthread_mutex_lock(&mutex);
   }

   printf("All threads completed.\n");
}

// called by any thread to create another
void CreateThread()
{
   pthread_t id;

   pthread_mutex_lock(&mutex);
   pthread_create(&id, NULL, ThreadInit, &id); // pass the id so the thread can use it with to remove itself
   threadIdList.add(id);
   pthread_mutex_unlock(&mutex);  
}

// called by each thread before it dies
void RemoveThread(pthread_t& id)
{
   pthread_mutex_lock(&mutex);
   threadIdList.remove(id);
   pthread_mutex_unlock(&mutex);
}
1
Nick Sotiros

Merci à tous pour les bonnes réponses! Il a été beaucoup question d'utiliser des barrières de mémoire, etc. - alors j'ai pensé que je posterais une réponse qui leur montrerait correctement qu'ils sont utilisés pour cela. 

#define NUM_THREADS 5

unsigned int thread_count;
void *threadfunc(void *arg) {
  printf("Thread %p running\n",arg);
  sleep(3);
  printf("Thread %p exiting\n",arg);
  __sync_fetch_and_sub(&thread_count,1);
  return 0L;
}

int main() {
  int i;
  pthread_t thread[NUM_THREADS];

  thread_count=NUM_THREADS;
  for (i=0;i<NUM_THREADS;i++) {
    pthread_create(&thread[i],0L,threadfunc,&thread[i]);
  }

  do {
    __sync_synchronize();
  } while (thread_count);
  printf("All threads done\n");
}

Notez que les macros __sync sont des macros internes GCC "non standard". LLVM les prend également en charge - mais si vous utilisez un autre compilateur, vous devrez peut-être faire quelque chose de différent.

Une autre chose importante à noter est la suivante: pourquoi voudriez-vous graver un noyau entier ou gaspiller la "moitié" d'un processeur dans une boucle de sondage serrée, en attendant que les autres aient fini - alors que vous pourriez facilement le mettre au travail? Le mod suivant utilise le thread initial pour exécuter l'un des ouvriers, puis attend que les autres se terminent:

  thread_count=NUM_THREADS;
  for (i=1;i<NUM_THREADS;i++) {
    pthread_create(&thread[i],0L,threadfunc,&thread[i]);
  }

  threadfunc(&thread[0]);

  do {
    __sync_synchronize();
  } while (thread_count);
  printf("All threads done\n");
}

Notez que nous commençons à créer les threads en commençant par "1" au lieu de "0", puis exécutons directement "thread 0" en ligne, en attendant que tous les threads soient terminés. Nous lui passons & [0] par souci de cohérence (même si cela n’a pas de sens ici), même si en réalité vous transmettriez probablement vos propres variables/contextes.

0
Brad