36

我只希望我的主线程在退出之前等待我的所有(p)线程完成。

由于不同的原因,线程来来去去很多,我真的不想跟踪所有这些 - 我只想知道它们什么时候都消失了。

wait() 对子进程执行此操作,当没有子进程时返回 ECHILD,但是 wait 不会(似乎可以使用)(p)线程。

我真的不想麻烦地保留每个未完成线程的列表(因为它们来来去去),然后不得不在每个线程上调用 pthread_join。

因为有一种快速而肮脏的方法来做到这一点?

4

5 回答 5

27

在所有线程完成后,您是否希望您的主线程特别做任何事情?

如果没有,您可以让您的主线程简单地调用pthread_exit()而不是返回(或调用exit())。

如果main()返回它隐式调用(或表现得好像它被调用)exit(),这将终止进程。但是,如果main()调用pthread_exit()而不是返回,exit()则不会发生隐式调用,并且进程不会立即结束 - 它会在所有线程终止时结束。

不能得到太多快速n-dirtier。

这是一个小示例程序,可以让您看到不同之处。传递-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;
}
于 2011-05-27T18:07:52.783 回答
23

正确的方法是跟踪你所有的 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);
  }
}
于 2011-05-27T17:04:36.763 回答
2

如果你不想跟踪你的线程,那么你可以分离线程,这样你就不必关心它们,但是为了知道它们何时完成,你必须走得更远一些。

一个技巧是保留线程状态的列表(链表、数组等)。当一个线程启动时,它会将其在数组中的状态设置为类似于 THREAD_STATUS_RUNNING 的值,并且在它结束之前,它会将其状态更新为类似于 THREAD_STATUS_STOPPED 的值。然后,当您想检查是否所有线程都已停止时,您可以遍历该数组并检查所有状态。

不要忘记,如果您执行此类操作,您将需要控制对数组的访问,以便一次只有一个线程可以访问(读取写入)它,因此您需要在其上使用互斥锁.

于 2011-05-27T15:50:02.037 回答
1

您可以保留所有线程 ID 的列表,然后对每个线程执行 pthread_join,当然您需要一个互斥锁来控制对线程 ID 列表的访问。您还需要某种可以在迭代时修改的列表,可能是 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);
}
于 2014-02-23T21:32:57.790 回答
0

感谢大家的精彩回答!有很多关于使用内存屏障等的讨论 - 所以我想我会发布一个正确显示它们用于此目的的答案。

#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 也支持这些 - 但如果您使用另一个编译器,您可能需要做一些不同的事情。

另一件需要注意的大事是:你为什么要烧掉整个内核,或者浪费“一半”的 CPU 在一个紧密的轮询循环中旋转,只是等待其他人完成——当你可以轻松地让它工作的时候?以下 mod 使用初始线程运行其中一个工作人员,然后等待其他工作人员完成:

  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”内联,等待所有线程完成后完成。我们将 &thread[0] 传递给它以保持一致性(即使它在这里毫无意义),尽管实际上您可能会传递您自己的变量/上下文。

于 2015-06-18T13:38:53.350 回答