【问题标题】:When to use pthread_exit() and when to use pthread_join() in Linux?在 Linux 中何时使用 pthread_exit() 以及何时使用 pthread_join()?
【发布时间】:2014-01-16 10:26:16
【问题描述】:

我是 pthreads 的新手,我正在努力理解它。我看到了一些类似下面的例子。

我可以看到 main() 被 API pthread_exit() 阻止,并且我看到了主要功能被 API pthread_join() 阻止的示例。我无法理解何时使用什么?

我指的是以下网站 - https://computing.llnl.gov/tutorials/pthreads/。我无法理解何时使用pthread_join() 以及何时使用pthread_exit()

有人可以解释一下吗?此外,我们将不胜感激 pthreads 的良好教程链接。

#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS     5

void *PrintHello(void *threadid)
{
   long tid;
   tid = (long)threadid;
   printf("Hello World! It's me, thread #%ld!\n", tid);
   pthread_exit(NULL);
}

int main (int argc, char *argv[])
{
   pthread_t threads[NUM_THREADS];
   int rc;
   long t;
   for(t=0; t<NUM_THREADS; t++){
      printf("In main: creating thread %ld\n", t);
      rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);
      if (rc){
         printf("ERROR; return code from pthread_create() is %d\n", rc);
         exit(-1);
      }
   }

   /* Last thing that main() should do */
   pthread_exit(NULL);

意识到另一件事,即

pthread_cancel(thread);
pthread_join(thread, NULL);

有时,您想在线程执行时取消它。 您可以使用 pthread_cancel(thread); 来执行此操作。 但是,请记住您需要启用 pthread 取消支持。 此外,取消时的清理代码。

thread_cleanup_push(my_thread_cleanup_handler, resources);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0);

static void my_thread_cleanup_handler(void *arg)
{
  // free
  // close, fclose
}

【问题讨论】:

    标签: c linux pthreads


    【解决方案1】:

    pthread_exit 终止调用线程,而pthread_join 暂停调用线程的执行,直到目标线程完成执行。

    开放组文档中对它们进行了很好的详细解释:

    【讨论】:

    • 但是你看到了吗,在 main() 中,我调用了 pthread_exit()。这阻止了 main() 的终止,并使线程运行并完成。这样,它与 pthread_join() 非常相似。此外,pthread_join() 会阻止 main() 的终止,直到和除非线程被执行。
    • 你知道有什么好的链接可以开始了解 pthread 吗?
    • @BasileStarynkevitch,为什么不呢。这是一个定义明确的用例。
    【解决方案2】:

    您不需要在特定代码中调用pthread_exit(3)

    一般来说,main 线程应该调用pthread_exit,但应该经常调用pthread_join(3)等待其他线程完成。 p>

    在您的PrintHello 函数中,您不需要调用pthread_exit,因为它在返回后是隐式的。

    所以你的代码应该是:

    void *PrintHello(void *threadid)  {
      long tid = (long)threadid;
      printf("Hello World! It's me, thread #%ld!\n", tid);
      return threadid;
    }
    
    int main (int argc, char *argv[]) {
       pthread_t threads[NUM_THREADS];
       int rc;
       intptr_t t;
       // create all the threads
       for(t=0; t<NUM_THREADS; t++){
         printf("In main: creating thread %ld\n", (long) t);
         rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);
         if (rc) { fprintf(stderr, "failed to create thread #%ld - %s\n",
                                    (long)t, strerror(rc));
                   exit(EXIT_FAILURE);
                 };
       }
       pthread_yield(); // useful to give other threads more chance to run
       // join all the threads
       for(t=0; t<NUM_THREADS; t++){
          printf("In main: joining thread #%ld\n", (long) t);
          rc = pthread_join(&threads[t], NULL);
          if (rc) { fprintf(stderr, "failed to join thread #%ld - %s\n",
                                    (long)t, strerror(rc));
                   exit(EXIT_FAILURE);
          }
       }
    }
    

    【讨论】:

      【解决方案3】:

      这两种方法都确保您的进程不会在所有线程结束之前结束。

      join 方法让main 函数的线程显式等待所有要“加入”的线程。

      pthread_exit 方法以受控方式终止您的main 函数和线程。 main 具有结束 main 的特殊性,否则将终止您的整个进程,包括所有其他线程。

      为此,你必须确保你的线程没有使用在它们内部声明的局部变量 main 函数。该方法的优点是您的main 不必知道您的进程中已启动的所有线程,例如因为其他线程自己创建了main 不知道的新线程。

      【讨论】:

      • 不是很清楚,你是说 pthread_exit 会阻止 main() 函数终止,以便其他线程工作有机会并且工作正常。你能告诉我一个关于 pthreads 的好链接吗?看起来,我缺少基础知识。
      • @SHREYASJOSHI,不,它没有阻止main 函数。 main 函数的 thread 将由此终止。您似乎在混淆 main 函数、它的执行线程和重新组合 所有 线程的进程。 main 函数有两个特殊之处:它是启动进程的第一个线程,如果它以returnexit 结束,则终止整个进程。但是,如果您以pthread_exit 结束它,则该线程将结束,而其他线程将保留。
      【解决方案4】:

      嗯。

      POSIX pthread_exit 来自http://pubs.opengroup.org/onlinepubs/009604599/functions/pthread_exit.html 的描述:

      After a thread has terminated, the result of access to local (auto) variables of the thread is 
      undefined. Thus, references to local variables of the exiting thread should not be used for 
      the pthread_exit() value_ptr parameter value.
      

      这似乎与本地 main() 线程变量仍可访问的想法相反。

      【讨论】:

        【解决方案5】:

        如 openpub 文档中所述,

        pthread_exit() 将退出调用它的线程。

        在您的情况下,由于 main 调用它, main thread 将终止,而您生成的线程将继续执行。这主要用于以下情况 主线程只需要生成线程并让线程完成它们的工作

        pthread_join 除非目标线程终止,否则将暂停调用它的线程的执行

        这在您想等待线程终止后再进一步的情况下很有用 在主线程中处理。

        【讨论】:

        • 自从您回答了这个问题后,实施可能发生了变化。根据 dexterous 所指的站点,通过让 main() 明确调用 pthread_exit() 作为它所做的最后一件事,main 将阻塞并保持活动状态以支持它创建的线程,直到它们完成。也就是说,一个线程不能存在于 main() 的主线程之外。
        【解决方案6】:

        pthread_exit() API

        如前所述,用于调用线程终止。 调用该函数后,将启动复杂的清理机制。 当它完成时,线程被终止。 当在 pthread_create() 创建的线程中调用 return() 例程时,也会隐式调用 pthread_exit() API。 实际上,对 return() 的调用和对 pthread_exit() 的调用具有相同的影响,都是从 pthread_create() 创建的线程调用的。

        区分初始线程、main() 函数启动时隐式创建的线程和 pthread_create() 创建的线程非常重要。 从 main() 函数调用 return() 例程会隐式调用 exit() 系统调用,并且整个进程终止。 没有启动线程清理机制。 从 main() 函数调用 pthread_exit() 会导致清理机制启动,当它完成其工作时,初始线程终止。

        当从 main() 函数调用 pthread_exit() 时,整个进程(以及其他线程)会发生什么取决于 PTHREAD 实现。 例如,在 IBM OS/400 实现中,当从 main() 函数调用 pthread_exit() 时,整个进程都会终止,包括其他线程。 其他系统的行为可能不同。 在大多数现代 Linux 机器上,从初始线程调用 pthread_exit() 不会终止整个进程,直到所有线程终止。 如果您想编写可移植的应用程序,请小心使用 main() 中的 pthread_exit()。

        pthread_join() API

        是一种等待线程终止的便捷方式。 您可以编写自己的函数来等待线程终止,这可能更适合您的应用程序,而不是使用 pthread_join()。 例如,它可以是一个基于等待条件变量的函数。

        我建议阅读 David R. Butenhof “使用 POSIX 线程编程”一书。 它很好地解释了讨论的主题(以及更复杂的事情)(尽管一些实现细节,例如 main 函数中的 pthread_exit 使用,并不总是反映在书中)。

        【讨论】:

          【解决方案7】:

          pthread_exit() 将终止调用线程并退出(但调用线程使用的资源如果不与主线程分离,则不会释放给操作系统。)

          pthrade_join() 将等待或阻塞调用线程,直到目标线程未终止。 简而言之,它将等待退出目标线程。

          在您的代码中,如果您在PrintHello 函数中将睡眠(或延迟)放在pthread_exit() 之前,则主线程可能会退出并终止整个进程,尽管您的PrintHello 函数未完成它会终止。如果在从 main 调用 pthread_exit() 之前在 main 中使用 pthrade_join() 函数,它将阻塞主线程并等待完成调用线程 (PrintHello)。

          【讨论】:

            【解决方案8】:

            在主线程中使用pthread_exit(代替pthread_join),将使主线程处于失效(僵尸)状态。由于不使用pthread_join,其他被终止的可加入线程也将保持僵尸状态,导致资源泄漏

            未能加入可加入的线程(即, 未分离),产生一个“僵尸线程”。避免这样做,因为 每个僵尸线程都会消耗一些系统资源,当足够时 僵尸线程已经积累,将不再可能 创建新线程(或进程)。

            另一点是保持主线程处于失效状态,而其他线程正在运行可能会在各种情况下导致与实现相关的问题,例如是否在主线程中分配资源或在其他线程中使用主线程本地的变量.

            此外,所有共享资源只有在进程退出时才会释放,它不会节省任何资源。所以,我认为应该避免使用pthread_exit 代替pthread_join

            【讨论】:

              【解决方案9】:

              当调用 pthread_exit() 时,调用线程堆栈不再可寻址为任何其他线程的“活动”内存。 “静态”内存分配的 .data、.text 和 .bss 部分仍然可供所有其他线程使用。因此,如果您需要将一些内存值传递给 pthread_exit() 以供其他 pthread_join() 调用者查看,则它需要“可用”以供调用 pthread_join() 的线程使用。它应该使用 malloc()/new 分配,分配在 pthread_join 线程堆栈上,1) pthread_join 调用者传递给 pthread_create 或以其他方式提供给调用 pthread_exit() 的线程的堆栈值,或 2) 分配的静态 .bss价值。

              了解如何在线程堆栈之间管理内存以及值存储在用于存储进程范围值的 .data/.bss 内存部分中是至关重要的。

              【讨论】:

                【解决方案10】:
                  #include<stdio.h>
                  #include<pthread.h>
                  #include<semaphore.h>
                 
                   sem_t st;
                   void *fun_t(void *arg);
                   void *fun_t(void *arg)
                   {
                       printf("Linux\n");
                       sem_post(&st);
                       //pthread_exit("Bye"); 
                       while(1);
                       pthread_exit("Bye");
                   }
                   int main()
                   {
                       pthread_t pt;
                       void *res_t;
                       if(pthread_create(&pt,NULL,fun_t,NULL) == -1)
                           perror("pthread_create");
                       if(sem_init(&st,0,0) != 0)
                           perror("sem_init");
                       if(sem_wait(&st) != 0)
                           perror("sem_wait");
                       printf("Sanoundry\n");
                       //Try commenting out join here.
                       if(pthread_join(pt,&res_t) == -1)
                           perror("pthread_join");
                       if(sem_destroy(&st) != 0)
                           perror("sem_destroy");
                       return 0;
                   }
                

                将此代码复制并粘贴到 gdb 上。 Onlinegdb 会工作,自己看看。

                确保你理解一旦你创建了一个线程,这个进程就会和main一起同时运行。

                1. 没有join,主线程继续运行,返回0
                2. 通过连接,主线程会卡在 while 循环中,因为它等待线程完成执行。
                3. 加入并删除注释掉的 pthread_exit,线程将在运行 while 循环之前终止,而 main 将继续
                4. pthread_exit 的实际用法可用作 if 条件或 case 语句,以确保某些代码的 1 个版本在退出之前运行。
                void *fun_t(void *arg)
                   {
                       printf("Linux\n");
                       sem_post(&st); 
                       if(2-1 == 1)  
                           pthread_exit("Bye");
                       else
                       { 
                           printf("We have a problem. Computer is bugged");
                           pthread_exit("Bye"); //This is redundant since the thread will exit at the end
                                                //of scope. But there are instances where you have a bunch
                                                //of else if here.
                       }
                   }
                
                

                我想演示在本示例中,有时您需要先使用信号量运行一段代码。

                #include<stdio.h>
                #include<pthread.h>
                #include<semaphore.h>
                
                sem_t st;
                
                void* fun_t (void* arg)
                {
                    printf("I'm thread\n");
                    sem_post(&st);
                }
                
                int main()
                {
                    pthread_t pt;
                    pthread_create(&pt,NULL,fun_t,NULL);
                    sem_init(&st,0,0);
                    sem_wait(&st);
                    printf("before_thread\n");
                    pthread_join(pt,NULL);
                    printf("After_thread\n");
                    
                }
                

                注意到 fun_t 在“线程前”之后是如何运行的 如果从上到下是线性的,则预期输出将在线程之前,我是线程,在线程之后。但是在这种情况下,我们会阻止 main 继续运行,直到 func_t 释放信号量。结果可以通过https://www.onlinegdb.com/进行验证

                【讨论】:

                • 在您的示例中,线程可能会在主线程调用 sem_init() 之前调用 sem_post()。
                • 这也是演示信号量的重点。 sem_init(&st,0,0) 后跟 sem_wait(&st) 将停止 main 继续执行,直到 sem_post 被调用。但是如果 sem_post 在 main 后面,则不会调用它,因为它仍在等待中。 pt 和 main 同时在一边。当 init 和 wait 发生在主线程上时,pt 正在运行 sem_post。现在信号量被释放。你可以在调试器上试一试。注释掉 sem_post 行,将 sem_post 行移到 wait 后面,移到 wait 之前看看区别
                • 无论如何,这不是一个正确的程序:我们不会在没有初始化的情况下使用资源。即使你很幸运,因为全局变量被初始化为 0 并且这可能是 sem_init() 的初始化所做的,但在初始化之前使用资源肯定是不正确的,并且你的程序不能保证线程不会调用 sem_post () 在主线程调用 sem_init() 之前。
                猜你喜欢
                • 1970-01-01
                • 2012-01-20
                • 2015-01-29
                • 2016-10-31
                • 1970-01-01
                • 1970-01-01
                • 2015-09-06
                • 2021-05-31
                相关资源
                最近更新 更多