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

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

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

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

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

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

Ответ 1

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

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

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

Ответ 3

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

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

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

Ответ 4

вы можете сохранить список всех ваших идентификаторов потоков, а затем выполнить pthread_join на каждом из них, конечно, вам понадобится мьютекс, чтобы контролировать доступ к списку идентификаторов потоков. ты будешь также нужен какой-то список, который может быть изменен при повторении, возможно, 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);
}

Ответ 5

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

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