【问题标题】:How do you query a pthread to see if it is still running?如何查询 pthread 以查看它是否仍在运行?
【发布时间】:2011-01-10 12:16:48
【问题描述】:

在我的析构函数中,我想彻底销毁一个线程。

我的目标是等待线程完成执行然后销毁线程。

我发现关于查询 pthread 状态的唯一信息是pthread_attr_setdetachstate,但这只会告诉您您的线程是否是:

  • PTHREAD_CREATE_DETACHED
  • PTHREAD_CREATE_JOINABLE

这两者都与线程是否仍在运行无关。

如何查询 pthread 以查看它是否仍在运行?

【问题讨论】:

  • 10 年了,但它就在这里......你是什么意思“等待线程完成执行然后销毁线程”?当一个线程完成执行时,它会被销毁。

标签: c multithreading concurrency pthreads destructor


【解决方案1】:

听起来你有两个问题:

我怎样才能等到我的线程完成?

答案:这由 pthreads 直接支持——让你的待停止线程 JOINABLE(当它第一次启动时),并使用 pthread_join() 来阻塞你当前的线程,直到待停止的线程不再运行。


如何判断我的线程是否仍在运行?

答案:您可以添加一个“thread_complete”标志来解决问题:

场景:线程 A 想知道线程 B 是否还活着。

当线程 B 被创建时,它被赋予一个指向“thread_complete”标志地址的指针。在创建线程之前,应将“thread_complete”标志初始化为 NOT_COMPLETED。线程 B 的入口点函数应该立即调用 pthread_cleanup_push() 来推送一个“清理处理程序”,它将“thread_complete”标志设置为 COMPLETED。

在此处查看有关清理处理程序的详细信息:pthread cleanup handlers

您需要包含一个相应的 pthread_cleanup_pop(1) 调用,以确保无论如何都会调用清理处理程序(即,如果线程正常退出或由于取消等)。

然后,线程 A 可以简单地检查“thread_complete”标志以查看线程 B 是否已经退出。

注意:您的“thread_complete”标志应该被声明为“volatile”并且应该是原子类型——GNU 编译器为此提供了 sig_atomic_t。这允许两个线程一致地访问相同的数据,而无需同步构造(互斥体/信号量)。

【讨论】:

  • 完全不需要清理处理程序,除非您将使用取消(这通常是一个相当危险的工具)。此外,您可能希望使用 pthread 条件变量和 flag 变量来启用等待它而无需旋转和烧毁 cpu 周期。
  • 如果目标是知道是否调用 join 来“销毁” pthread_t 句柄,检查标志将不起作用,因为当您检查标志时线程可能尚未终止但仍在管理在加入之前终止(注意加入已加入的线程会导致未定义的行为,不加入可加入的线程会导致泄漏。)
  • 对于所有想要使用上述解决方案的人,我在下面回答了这个解决方案的问题。在应用到您的解决方案之前阅读它。在很多情况下这可能不是问题,但您可能想了解它。
  • @Emil 没有问题。只需确保调用一次且仅一次。比赛是无害的——是的,如果您碰巧在设置标志之后和线程终止之前检查标志,您的连接可能会阻塞一小部分时间,但线程无论如何都将终止。
  • 如果你计算了一些东西然后发信号thread_complete,所以计算结果可以使用,这是错误的volatile sig_atomic_t 仍然可以重新排序,因此它可能会在计算结果变得可见并且消费者可以看到无效状态之前写入。
【解决方案2】:
pthread_kill(tid, 0);

未发送信号,但仍会执行错误检查,因此您可以使用它来检查 tid的存在。

注意:此答案不正确。该标准明确禁止传递生命周期已结束的线程的 ID。该 ID 现在可能会指定不同的线程,或者更糟糕的是,它可能引用已释放的内存,从而导致崩溃。

【讨论】:

  • 对于我的问题,这正是我需要解决的问题,谢谢。我用过 (pthread_kill(tid, 0) != ESRCH)。
  • @DavidSchwartz 在这种情况下,最好按照here 的建议将您的编辑清楚地标记为不属于原始答案的内容。
  • 对 David Schwartz(正确)编辑的附加评论 - 手册页(Linux)这样说:'POSIX.1-2008 建议,如果实现在其生命周期结束后检测到线程 ID 的使用, pthread_kill() 应该返回错误 ESRCH。在可以检测到无效线程 ID 的情况下,glibc 实现会返回此错误。但还要注意,POSIX 表示尝试使用生命周期已结束的线程 ID 会产生未定义的行为,并且在调用 pthread_kill() 时尝试使用无效的线程 ID 可能会导致分段错误。'
【解决方案3】:

我认为您真正需要的只是调用 pthread_join()。在线程退出之前,该调用不会返回。

如果您只想轮询以查看线程是否仍在运行(请注意,这通常不是您应该做的!),您可以让线程在它之前将 volatile 布尔值设置为 false退出......然后你的主线程可以读取布尔值,如果它仍然是真的,你知道线程仍在运行。 (如果它是假的,另一方面,你知道线程至少几乎消失了;它可能仍在运行在将布尔值设置为假之后发生的清理代码,所以即使在这种情况下,你仍然应该在之前调用 pthread_join试图释放线程可能访问的任何资源)

【讨论】:

  • +1 用于解释所涉及的潜在竞争条件及其简单解决方案。
  • "那么你的主线程可以读取布尔值,如果它仍然是真的,你知道线程仍在运行。" -- 在这种情况下,你只会知道线程是否干净地退出,这与是否仍在运行是不一样的。
  • 嗯,我可能在这里错过了一种可能性,但是 AFAICT 线程要么仍在运行,要么已经退出,要么已经崩溃。如果线程崩溃了,它会占用剩下的进程,所以无论如何都没有第二个线程可以检测到。
【解决方案4】:

没有完全可移植的解决方案,看看你的平台是否支持 pthread_tryjoin_np 或 pthread_timedjoin_np。因此,您只需检查是否可以加入线程(当然是使用 PTHREAD_CREATE_JOINABLE 创建的)。

【讨论】:

  • 这些接口被 POSIX 拒绝,不应该在打算移植的应用程序中使用。同样的事情可以通过一个条件变量来实现。
  • 仅仅因为可以使用条件变量完成同样的事情并不意味着 pthread_tryjoin_np 不是一个更干净的接口。
  • 我正在使用条件变量 - 在线程结束时,就在 return NULL; 之前我已经清除了变量。但是由于未完成的线程太多,我的低 RAM 内存系统崩溃了。根本原因不清楚。但明确的是,这个变量不能作为线程退出的指标。
【解决方案5】:

让我注意“获胜”的答案,它有一个巨大的隐藏缺陷,在某些情况下它可能会导致崩溃。除非你使用 pthread_join,否​​则它会一次又一次地出现。假设您有一个进程和一个共享库。调用库 lib.so。

  1. 你打开它,你在里面启动一个线程。假设你不希望它加入它,所以你将它设置为可拆卸的。
  2. 处理和共享库的逻辑执行其工作等...
  3. 您想加载 lib.so,因为您不再需要它。
  4. 您在线程上调用了一个shutdown,然后您说您想在之后从您的lib.so 的线程中读取一个标志,它已经完成。
  5. 您继续使用 dlclose 处理另一个线程,因为您看到,您已经看到,标志现在将线程显示为“已完成”
  6. dlclose 将加载所有堆栈和代码相关的内存。
  7. 哎呀,但是 dlclose 不会停止线程。而且您知道,即使您在清理处理程序的最后一行设置“线程已完成”易失性原子标志变量,您仍然必须从堆栈上的许多方法返回,返回值等。如果#5+#6 的线程获得了巨大的线程优先级,在您真正停止线程之前,您将收到 dlclose。有时您会遇到一些不错的崩溃。

让我指出,这不是一个假设的问题,我在我们的项目中遇到了同样的问题。

【讨论】:

    【解决方案6】:

    我相信我已经提出了一个至少适用于 Linux 的解决方案。每当我创建一个线程时,我都会保存它的 LWP(轻量级进程 ID)并为其分配一个唯一的名称,例如。 int lwp = 系统调用(SYS_gettid); prctl(PR_SET_NAME, (long)"唯一名称", 0, 0, 0);

    然后,为了稍后检查线程是否存在,我打开 /proc/pid/task/lwp /comm 并阅读它。如果文件存在并且其内容与我分配的唯一名称匹配,则线程存在。请注意,这不会将可能已失效/重用的 TID 传递给任何库函数,因此不会发生崩溃。

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdarg.h>
    #include <pthread.h>
    #include <sys/prctl.h>
    #include <sys/file.h>
    #include <stdbool.h>
    #include <string.h>
    #include <unistd.h>
    #include <syscall.h>
    
    pthread_t subthread_tid;
    int       subthread_lwp;
    
    #define UNIQUE_NAME "unique name"
    
    bool thread_exists (pthread_t thread_id)
    {
        char path[100];
        char thread_name[16];
        FILE *fp;
        bool  thread_exists = false;
    
        // If the /proc/<pid>/task/<lwp>/comm file exists and it's contents match the "unique name" the
        // thread exists, and it's the original thread (TID has NOT been reused).
    
        sprintf(path, "/proc/%d/task/%d/comm", getpid(), subthread_lwp);
    
        fp = fopen(path, "r");
    
        if( fp != NULL ) {
    
            fgets(thread_name, 16, fp);
            fclose(fp);
    
            // Need to trim off the newline
            thread_name[strlen(thread_name)-1] = '\0';
    
            if( strcmp(UNIQUE_NAME, thread_name) == 0 ) {
                thread_exists = true;
            }
        }
    
        if( thread_exists ) {
            printf("thread exists\n");
        } else {
            printf("thread does NOT exist\n");
        }
    
        return thread_exists;
    }
    
    
    void *subthread (void *unused)
    {
        subthread_lwp = syscall(SYS_gettid);
        prctl(PR_SET_NAME, (long)UNIQUE_NAME, 0, 0, 0);
    
        sleep(10000);
    
        return NULL;
    }
    
    
    int main (int argc, char *argv[], char *envp[])
    {
        int error_number;
    
        pthread_create(&subthread_tid, NULL, subthread, NULL);
        printf("pthread_create()\n");
        sleep(1);
        thread_exists(subthread_tid);
    
        pthread_cancel(subthread_tid);
        printf("pthread_cancel()\n");
        sleep(1);
        thread_exists(subthread_tid);
    
        error_number = pthread_join(subthread_tid, NULL);
        if( error_number == 0 ) {
            printf("pthread_join() successful\n");
        } else {
            printf("pthread_join() failed, %d\n", error_number);
        }
        thread_exists(subthread_tid);
    
        exit(0);
    }
    

    【讨论】:

      【解决方案7】:
      #include <string.h>
      #include <stdio.h>
      #include <pthread.h>
      #include <signal.h>
      #include <unistd.h>
      
      void* thread1 (void* arg);
      void* thread2 (void* arg);
      
      int main()
      {
          pthread_t thr_id;
      
          pthread_create(&thr_id, NULL, thread1, NULL);
      
          sleep(10);
      }
      
      void* thread1 (void* arg)
      {
          pthread_t thr_id = 0;
      
          pthread_create(&thr_id, NULL, thread2, NULL);
      
          sleep(5);
          int ret = 0;
          if( (ret = pthread_kill(thr_id, 0)) == 0)
          {
              printf("still running\n");
              pthread_join(thr_id, NULL);
          }
          else
          {
              printf("RIP Thread = %d\n",ret);
          }
      }
      
      void* thread2 (void* arg)
      {
      //  sleep(5);
          printf("I am done\n");
      }
      

      【讨论】:

      • 虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。
      • 您可能没有意识到,但在死线程上执行 pthread_kill 可能会使您的应用程序崩溃或导致意外结果,例如释放用于其他事物的内存。 stackoverflow.com/a/1693235/105539
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-03-15
      • 1970-01-01
      • 1970-01-01
      • 2016-02-14
      • 1970-01-01
      • 2012-02-24
      相关资源
      最近更新 更多