如何等待所有/任意 pthread 完成?

36

我希望我的主线程在退出之前等待所有(p)线程完成,这些线程由于不同的原因经常出现和消失,我真的不想跟踪它们的状态 - 我只想知道它们何时结束。

wait()可以用于子进程,当没有子进程时返回ECHILD,但是似乎不适用于(p)线程。

我真的不想麻烦地记录每一个未完成的线程(因为它们来了又走),然后对每个线程调用pthread_join。

有没有一种简单粗暴的方法可以做到这一点?

5个回答

27

如果您主线程不需要在所有线程完成后执行任何特定操作,那么您可以让主线程简单地调用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;
}

1
谢谢回复!实际上,是的,主线程需要清理/删除共享内存段 - 所以我不能像你描述的那样只调用pthread_exit。(我现在意识到我应该在原始帖子中说明这一点)。感谢您的回复! - Brad
1
@Sammaron:如果调用了 pthread_exit(),那么 main() 结尾处的 return 语句将不会被执行。线程已经退出,但是没有执行拆除进程的任何机制。操作系统将在进程中的所有线程退出后(或者另一个线程可以调用类似于 exit() 的函数来终止进程)执行此操作。 - Michael Burr
不释放tcb会导致内存泄漏吗? - razz
@razzak:是的。这个程序只是一个简单的例子,用来演示从主程序调用exit()(或让main()返回)和调用pthread_exit()之间的区别。它并不意味着展示线程资源完整正确的处理方式。 - Michael Burr
@MichaelBurr 谢谢,我看到你的代码片段时以为 pthread_exit() 会自动释放线程。 - razz
显示剩余2条评论

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

4
使用屏障(barrier)而不是计数器和互斥锁,可以更轻松地实现这一点。 - R.. GitHub STOP HELPING ICE
我喜欢这个解决方案 - 我没有想到只需计算运行实例。我认为,由于所有操作都是原子操作,因此甚至可以省去互斥体的需要。 - SlappyTheFish
2
这些操作绝对不是原子的。互斥锁是必不可少的。不过可以查一下屏障;它们更容易使用,而且可以为您计数。 - R.. GitHub STOP HELPING ICE
1
我不喜欢这个解决方案——因为它意味着我必须保持轮询和休眠——但它确实是最简单的!实际上,除非我收到关闭信号,否则我不会陷入while/sleep循环中——因此在现实世界中它不会真正消耗任何CPU周期。 - Brad
1
@SlappyTheFish 中间状态可能是将要增加的值加载到寄存器中。你不能确定增量直接针对内存发生。 - unwind
显示剩余2条评论

2
如果您不想跟踪线程,可以将它们分离,这样您就不必关心它们,但是为了知道它们何时完成,您需要更进一步。一个技巧是保持线程状态的列表(链接列表、数组等)。当线程启动时,它会将其状态设置为 THREAD_STATUS_RUNNING,并在结束之前将其状态更新为 THREAD_STATUS_STOPPED。然后,当您想要检查所有线程是否已停止时,只需遍历此数组并检查所有状态即可。
但请注意,如果您这样做,您需要控制对该数组的访问,以便只有一个线程可以同时访问(读取和写入),因此您需要在其上使用互斥锁。

这个解决方案并没有使事情变得更简单。如果你要创建那个丑陋的数组(顺便说一下,你需要同步它!),你可以考虑只存储 pthread_t 的标识符,并使用 pthread_join 加入它们。 - R.. GitHub STOP HELPING ICE
1
你是对的 - 但是OP确实说过线程会来去,他不想跟踪它们,我理解为他不想加入所有线程,而只是能够在退出条件出现时等待所有当前线程结束。正如gravitron建议的那样,计算运行中的线程会更简单并避免同步,但数组方法增加了灵活性(如果需要),以便每个线程可以由包含更多信息的结构体描述,例如何时启动等,可能有助于线程监视。 - SlappyTheFish
无论如何,你的方法存在严重的错误。一个线程不能将自己的状态设置为“THREAD_STATUS_RUNNING”,因为在设置之前会存在竞争条件。相反,在调用“pthread_create”之前,创建线程需要这样做。要使您的方法有效,需要进行更多的同步。如果您不是该领域的专家,“pthread_join”(或屏障)将是一个更简单、更少出错的解决方案。 - R.. GitHub STOP HELPING ICE
绝对没错 - 你说得对,你提出的避免竞争条件的方法很好。实际上,在我的一个项目中,我就是用这种方法解决的。我认为 gravitron 或 Michael Burr 提供的解决方案更符合 OP 的要求,我只是提供了一些思路 :-) - SlappyTheFish
我不能只是调用detach - 因为在所有线程都静止并退出后,我需要在我的主线程中进行一些清理工作。你说得对,我希望有比跟踪(锁定、同步)和重新加入所有工作线程更简单的方法。感谢回复! - Brad

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

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 在一个紧密的轮询循环中等待其他任务完成 - 当你可以轻松地让它工作呢?以下修改使用初始线程来运行其中一个工作线程,然后等待其他线程完成:
  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]传递给它,但实际上您可能会传递自己的变量/上下文。请注意,我们从“1”开始创建线程,而不是“0”,然后直接运行“线程0”内联,在完成后等待所有线程完成。为了保持一致性(尽管在这里没有意义),我们将&thread [0]传递给它,但实际上您可能会传递自己的变量/上下文。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接