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