Как я могу ждать завершения любых / всех pthreads?

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

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

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

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

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

5 ответов


правильный способ-отслеживать все ваши 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);
  }
}

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

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

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

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

вот пример небольшой программы, которая позволит вам увидеть разницу. Пас -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;
}

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

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

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


вы можете сохранить список всех идентификаторов потоков, а затем сделать 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);
}

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

#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 поддерживает их тоже-но если вы используете другой компилятор, вам может потребоваться сделать что-то другое.

еще одна большая вещь, которую нужно отметить: почему вы сжигаете все ядро или тратите "половину" процессора, вращающегося в tight poll-loop просто ждет, пока другие закончат - когда вы можете легко заставить его работать? Следующий мод использует начальный поток для запуска одного из рабочих, а затем дождитесь завершения других:

  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 " inline, ожидая завершения всех потоков после его завершения. Мы передаем &thread[0] ему для согласованности (хотя здесь это бессмысленно), хотя на самом деле вы, вероятно, передайте свои собственные переменные / контекст.