Как я могу ждать завершения всех / всех потоков? - PullRequest
34 голосов
/ 27 мая 2011

Я просто хочу, чтобы мой главный поток дождался завершения всех моих (p) потоков перед выходом.

Потоки приходят и уходят по разным причинам, и я действительно не хочу отслеживать их все - я просто хочу знать, когда они все ушли.

wait () делает это для дочерних процессов, возвращая ECHILD, когда не осталось дочерних процессов, однако wait не (кажется, не работает) с (p) потоками.

Я действительно не хочу проходить через проблемувести список каждого отдельного ожидающего потока (по мере того, как они приходят и уходят), а затем вызывать pthread_join для каждого.

Как существует быстрый и грязный способ сделать это?

Ответы [ 5 ]

26 голосов
/ 27 мая 2011

Хотите ли вы, чтобы ваш основной поток что-то делал, в частности, после завершения всех потоков?

Если нет, ваш основной поток может просто вызвать pthread_exit() вместо возврата (или вызвать exit()).

Если main() возвращает его, неявно вызывает (или ведет себя так, как если бы он вызвал) exit(), что завершит процесс. Однако, если main() вызывает pthread_exit() вместо возврата, этот неявный вызов exit() не происходит и процесс не завершится немедленно - он закончится, когда все потоки завершатся.

Не может быть слишком быстро-н-грязнее.

Вот небольшой пример программы, которая позволит вам увидеть разницу. Передайте -DUSE_PTHREAD_EXIT компилятору, чтобы увидеть, как процесс ожидает завершения всех потоков. Компилировать без определенного макроса, чтобы увидеть, как процесс останавливает потоки в своих дорожках.

#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;
}
23 голосов
/ 27 мая 2011

Правильный способ - отслеживать все ваши pthread_id, но вы попросили быстрый и грязный способ, так что вот оно.В основном:

  • просто храните общее количество запущенных потоков,
  • увеличивайте его в главном цикле перед вызовом pthread_create,
  • уменьшайте количество потоков по окончании каждого потока,
  • Затем спите в конце основного процесса, пока счет не вернется к 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);
  }
}
2 голосов
/ 27 мая 2011

Если вы не хотите отслеживать свои потоки, вы можете отсоединить потоки, чтобы вам не приходилось о них заботиться, но чтобы узнать, когда они закончены, вам придется пойти немного дальше.

Один трюк - сохранить список (связанный список, массив и т. Д.) Статусов потоков.Когда поток запускается, он устанавливает свой статус в массиве как THREAD_STATUS_RUNNING, и непосредственно перед его завершением он обновляет свой статус до THREAD_STATUS_STOPPED.Затем, когда вы захотите проверить, все ли потоки остановились, вы можете просто перебрать этот массив и проверить все статусы.

Не забывайте, однако, что если вы сделаете что-то подобное, вам нужно будет контролировать доступ кмассив, так что только один поток может получить к нему доступ (чтение и запись) одновременно, поэтому вам нужно использовать мьютекс.

1 голос
/ 24 февраля 2014

вы можете сохранить список всех ваших идентификаторов потоков, а затем сделать pthread_join для каждого, Конечно, вам понадобится мьютекс для управления доступом к списку идентификаторов потоков. ты сможешь также нужен какой-то список, который можно изменить во время итерации, например, std :: set ?

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);
}
0 голосов
/ 18 июня 2015

Спасибо всем за отличные ответы!Было много разговоров об использовании барьеров памяти и т. Д., Поэтому я решил опубликовать ответ, который правильно показал бы, как они используются для этого.

#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");
}

Обратите внимание, что макросы __sync являются «нестандартными» внутренними макросами GCC.LLVM также поддерживает их, но если вы используете другой компилятор, вам, возможно, придется сделать что-то другое.

Еще одна важная вещь, на которую следует обратить внимание: зачем вам записывать все ядро ​​или тратить «половину» вращения ЦП?в тесном цикле опроса, просто ожидая, пока другие закончат - когда вы могли бы легко заставить его работать?Следующий мод использует начальный поток для запуска одного из рабочих, а затем дождитесь завершения остальных:

  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");
}

Обратите внимание, что мы начинаем создавать потоки, начиная с "1" вместо "0", затемнапрямую запустить «поток 0», ожидая завершения всех потоков после его завершения.Мы передаем & thread [0] к нему для согласованности (хотя здесь это бессмысленно), хотя в действительности вы, вероятно, передадите свои собственные переменные / контекст.

...