【问题标题】:How can I wait for any/all pthreads to complete?如何等待任何/所有 pthread 完成?
【发布时间】:2011-09-03 12:22:37
【问题描述】:

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

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

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

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

因为有一种快速而肮脏的方法吗?

【问题讨论】:

    标签: c linux multithreading pthreads posix-api


    【解决方案1】:

    您是否希望您的主线程在所有线程都完成后执行特定的操作?

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

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

    不能得到太多的快速-n-脏。

    这是一个小示例程序,可让您了解其中的不同之处。将-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;
    }
    

    【讨论】:

    • 感谢您的回复!实际上 - 是的 - 主线程需要清理/删除共享内存段 - 所以我不能像你描述的那样只调用 pthread_exit 。 (我现在意识到我应该在 OP 中说明这一点)。感谢您的回复!
    • 可爱的答案。我对为什么pthread_exit 语句之后的return 语句没有结束该过程感到有些困惑。我们现在是否有 2 个不同的进程正在运行,一个被终止,另一个没有?我的(可能是错误的)印象是,从 main 返回至少会破坏 一些 进程。
    • @Sammaron:如果调用了pthread_exit(),则永远不会执行main() 末尾的return 语句。线程已退出,但没有执行任何拆除进程的机制。一旦进程中的所有线程都退出,操作系统就会这样做(或者另一个线程可以调用像exit() 这样的函数来终止进程)。
    • 不会释放tcb不会导致内存泄漏?
    • @razzak:是的。这个程序只是一个简单的例子来演示调用exit()(或让main()返回)和从主程序调用pthread_exit()之间的区别。这并不是为了展示对线程资源的完全正确处理。
    【解决方案2】:

    正确的方法是跟踪所有 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);
      }
    }
    

    【讨论】:

    • 这可以通过屏障而不是计数器和互斥体更容易地完成。
    • 我喜欢这个解决方案 - 我没有想到只是简单地计算正在运行的实例。我认为您甚至可以消除对互斥锁的需求,因为操作都是原子的,IINM。
    • 这些操作绝对不是原子的。互斥锁是必不可少的。不过要查找障碍;它们更易于使用并为您计算。
    • 我刚刚查了一下,是的,它不是原子的。不过很有趣——增量操作的中间状态是什么?部分设置/取消设置位?无论如何,感谢关于障碍的说明 - 我正在调查它们,它们听起来很有趣。
    • 我不喜欢这个解决方案——因为这意味着我必须继续轮询和休眠——但它确实是最简单的!实际上 - 在收到关闭信号之前,我不会陷入 while/sleep 循环 - 因此它不会真正消耗现实世界中的任何 CPU 周期。
    【解决方案3】:

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

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

    不要忘记,如果你做这样的事情,你需要控制对数组的访问,以便一次只有一个线程可以访问(读取写入)它,所以您需要在其上使用互斥锁。

    【讨论】:

    • 这个解决方案并没有让事情变得更容易。如果您要制作那个丑陋的数组(顺便说一下,您需要同步它!),您可以将pthread_t ids 和pthread_join 全部存储在其中。
    • 你是对的 - 但 OP 确实说线程来来去去,他不想跟踪它们,我解释为他不想加入所有线程,但只是能够在退出条件出现时等待所有当前线程结束。正如 gravitron 所建议的那样,计算正在运行的线程会更简单并避免同步,但是数组方法增加了灵活性(如果需要的话),因此每个线程都可以由包含更多信息的结构来描述,例如它何时启动等可能有帮助带有线程监控。
    • 无论如何,你的方法有严重的错误。线程不能将自己的状态设置为THREAD_STATUS_RUNNING,因为在设置之前会有竞争条件。相反,创建线程需要在调用pthread_create 之前执行此操作。要使您的方法有效,还需要进行更多同步。如果您不是该领域的专家,pthread_join(或障碍)将是一个更简单且不易出错的解决方案。
    • 绝对 - 再次,你是对的,你建议的避免竞争条件的方法是正确的。事实上,这正是我在我的一个项目中解决它的方法。我认为 gravitron 或 Michael Burr 的解决方案更符合 OP 的要求,我只是在思考:-)
    • 我不能只调用 detach - 因为在所有线程都停止并退出后,我需要在我的主线程中进行一些清理。你是对的,因为我希望有一些比跟踪(锁定、同步)和重新加入所有工作线程更简单的东西。感谢您的回复!
    【解决方案4】:

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

    【讨论】:

      【解决方案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 也支持这些 - 但如果您使用其他编译器,您可能需要做一些不同的事情。

      要注意的另一件大事是:为什么要烧掉整个内核,或者浪费 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] 传递给它以保持一致性(即使它在这里毫无意义),但实际上您可能会传递自己的变量/上下文。

      【讨论】:

        猜你喜欢
        • 2011-03-17
        • 1970-01-01
        • 2017-04-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-02-22
        • 2015-04-17
        相关资源
        最近更新 更多