【问题标题】:Waiting for threads of another process using waitpid使用 waitpid 等待另一个进程的线程
【发布时间】:2012-07-02 23:07:08
【问题描述】:

我正在尝试使用waitpid() 来等待单个线程而不是进程。我知道pthread_join()std::thread::join() 是等待线程的典型方式。然而,就我而言,我正在开发一个监控应用程序,它派生并执行(通过execv)一个程序,该程序反过来又产生一些线程。因此,我无法从监控应用程序中加入线程,因为它们属于不同的进程并且我无权访问源代码。不过,我希望能够等待这些单独的线程完成。

为了更容易地可视化我想要实现的目标,我附上了一张图,希望能更清楚:

当我使用进程时一切正常,但waitpid 不等待线程。基本上,waitpid 在被调用后立即返回-1(此时线程仍在运行几秒钟)。

waitpid 的文档说明:

在 Linux 内核中,内核调度的线程不是与进程不同的构造。相反,线程只是一个使用 Linux 独有的 clone(2) 系统调用创建的进程;其他例程,例如可移植的 pthread_create(3) 调用是使用 clone(2) 实现的。在 Linux 2.4 之前,线程只是进程的一种特殊情况,因此一个线程不能等待另一个线程的子线程,即使后者属于同一个线程组。但是,POSIX 规定了这样的功能,从 Linux 2.4 开始,线程可以并且默认情况下会等待同一线程组中其他线程的子线程。

该描述仅考虑从一个线程等待其他线程的子线程(在我的情况下,我想等待另一个进程的线程子线程)。但是,至少,它表明waitpid 是线程感知的。

这是我用来等待线程的:

std::vector<pid_t> pids;

/* fill vector with thread IDs (LWP IDs) */

for (pid_t pid : pids) {
    int status;
    pid_t res = waitpid(pid, &status, __WALL);
    std::cout << "waitpid rc: " << res << std::endl;
}

此代码适用于等待进程,但它等待线程失败(即使使用了__WALL 标志)。

我想知道是否真的可以使用waitpid 等待线程。我还需要使用其他标志吗?您能否指出任何解释如何等待另一个进程的线程的文档?

作为参考,我用于创建线程的代码是:

static void foo(int seconds) {
    int tid;
    {
        std::lock_guard<std::mutex> lock(mutex);
        tid = syscall(__NR_gettid);
        std::cout << "Thread " << tid << " is running\n";
        pids.push_back(tid);
        pids_ready.notify_all();
    }

    for (int i = 0; i < seconds; i++)
        std::this_thread::sleep_for(std::chrono::seconds(1));
}

static void create_thread(int seconds) {
    std::thread t(foo, seconds);
    threads.push_back(std::move(t));
}

std::vector<pid_t> create_threads(int num, int seconds) {
    for (int i = 0; i < num; i++)
        create_thread(seconds);

    std::unique_lock<std::mutex> lock(mutex);
    pids_ready.wait(lock, [num]() { return pids.size() == num; });

    return pids;
}

我使用的是 GCC 4.6 和 Ubuntu 12.04。

更新:我设法通过使用ptrace

ptrace(PTRACE_ATTACH, tid, NULL, NULL);
waitpid(tid, &status, __WALL);
ptrace(PTRACE_CONT, tid, NULL, NULL);

while (true) {
    waitpid(tid, &status, __WALL);
    if (WIFEXITED(status)) // assume it will exit at some point
        break;
    ptrace(PTRACE_CONT, tid, NULL, NULL);
}

此代码在 T1、T2、...、Tn 是进程和线程时都有效。

但是,我有一个问题。如果我在多线程 C++ 应用程序中尝试使用此监控工具,一切正常。但最初的意图是将此监视工具用于生成多个线程的 Java 应用程序。当使用多线程 Java 应用程序时,循环中的 waitpid 每秒会唤醒很多次(子线程被 SIGSEGV 信号停止)。这似乎与 Java 出于自己的目的使用 SIGSEGV 的事实有关(请参阅 this questionthis post)。

所有这些唤醒最终都会大大降低应用程序的速度。所以我想知道我的解决方案是否存在缺陷,是否有办法让它与 Java 应用程序一起工作。

【问题讨论】:

  • 虽然不熟悉使用 waitpid() 来等待线程终止,但我会解释您发布的 man 2 waitpid 的引用,以便人们可以使用 waitpid(..., __WALL) 在创建线程的进程之外人们想要监视,因为它们是这个创建进程(主线程)的子进程。由于waitpid() 只等待孩子而不是孙子,我认为你走错了路。
  • @alk 是的,您实际上可能是对的。然而,在waitpid 手册页中,它声明了The following Linux-specific options are for use with children created using clone(2)...(参考__WALL 等)。所以,不知何故,似乎应该可以等待一个线程。无论如何,我会继续寻找解决方案,同时希望有人已经这样做过并发布解决方案:)
  • 为什么不测试我概述的/proc/PID/task/TID/ 扫描方法?它仅适用于 Linux,您不会收到通知,但扫描 /proc/PID/task/ 是一个非常轻量级的操作。你想要一个例子吗?

标签: c++ c linux multithreading waitpid


【解决方案1】:

据我所知,waitpid 仅用于处理指定的终止 subpro。而且当有很多subpro同时等待处理时,它比等待更安全。

【讨论】:

    【解决方案2】:

    在 Linux 中,您可以监视 /proc/PID/task/ 目录,该目录包含属于进程 PID 的每个线程的目录。

    不幸的是,inotify 接口在这里似乎没有帮助,因此您必须反复扫描 /proc/PID/task/ 目录以查找线程 ID。幸运的是,这似乎是最低的成本,特别是如果您每秒只进行十几次或最多几十次扫描。请注意,该目录将在线程退出时消失,而不是在线程被收割时消失。

    TID==PID 的一个线程是 Linux 中的原始进程。其他线程将按递增顺序获取 TID(当然,它们最终会环绕)。请注意,TID 与 pthreads 线程无关。要找出哪个 TID 将映射到哪个 pthread_t,正在运行的线程必须调用gettid()(实际上是syscall(SYS_gettid));否则很难仅根据 TID 或 /proc/PID/task/TID/ 内容来判断哪个线程是哪个线程。如果您只对线程周转感兴趣(如果/当创建和/或退出时),那么这个接口比例如更有效。 ptrace,虽然线程退出检测存在延迟(这取决于您的目录扫描间隔)。

    【讨论】:

      【解决方案3】:

      不能在 Linux 中的其他进程中等待线程,但线程组负责人(也称为主线程)除外。

      现代 Linux 内核中的sys_waitpid 是作为sys_wait4 的包装器实现的,而sys_wait4 又调用do_waitdo_wait 完成了等待进程的繁重工作(线程只是一种特殊的进程)。它仅遍历当前任务的已知子代,如果未指定 __WNOTHREAD,则遍历同一线程组中其他线程的子代。

      这里有趣的时刻是,使用clone 系统调用创建线程实际上将新创建线程的父级设置为被克隆进程的父级但是这个父级绝不是通知它刚刚获得了一个新孩子(它没有在其task 结构的列表中注册)。当克隆存在时,它也不会收到SIGCHLD,因为线程的退出信号由copy_process 设置为-1 - 实际复制进程的函数。

      这背后的原理很简单:等待是单次操作 - 一旦执行并完成等待,等待的进程就不再存在。如果您允许另一个进程在线程或当前进程的子进程上等待,则您将从当前进程中获得对其子进程执行等待的能力。您还创建了一个可能的竞争条件,并且绝对不会享受 pthread_join() 失败,因为其他进程已在您的一个线程上等待,对吗?

      【讨论】:

        【解决方案4】:

        好的,这不是解决方案,而是解释为什么我怀疑使用waitpid()的解决方案:

        1.1 在 Linux 下,使用clone() 创建的线程是创建它们的进程的子进程。

        1.2 在此之后,线程是grand- 进程 (A) 的子进程 (A) 创建了进程 (B),而进程 (B) 又创建了线程。

        2 waitpid() 不会触发任何终止孙子的信号 SIGCHLD

        所有这些都解释了为什么你的方法不起作用。

        【讨论】:

          【解决方案5】:

          我对您声称所有流程都“正常工作”的说法有些困惑。 waitpid 只能等待你自己的子进程,而不是任意的其他进程,事实上,除非它是你自己的子进程,否则使用进程 id 几乎肯定是一个错误。

          与其寻找丑陋的 hack 来做一些原本不可能的事情,为什么不直接修复您的设计以使用一些适当的进程间通信机制,以便线程在完成时可以向其他进程发出信号?还是将整个程序放在一个进程中(具有多个线程),而不是将您的工作分散到多个进程和线程中?

          【讨论】:

          • 我用情节更新了这个问题,希望能让我的观点更清楚。如您所见,我的监控应用程序执行另一个程序。然后该程序创建线程,我想等待它们。我不能修改那个程序,所以我不能按照你的建议去做(我同意这是正确的做法)。如果不是生成线程的程序,而是监视生成进程的程序,那么waitpid 可以工作(即,它会等待子进程完成)。
          • 听起来你想使用ptrace,但这会产生相当大的运行时开销。
          • ptrace 我只用过一两次,所以不太熟悉。您是否知道运行时开销是否只会在线程创建/完成期间发生,还是会影响所有线程的总执行时间?
          • 实际上,我过去使用ptrace 是为了附加到在我的监控软件启动之前在系统中运行的进程。这样,我就可以等他们完成了。如果我需要等待我的监控应用程序的“孙子”进程/线程,我是否也需要使用ptrace?对于进程,似乎waitpid 就足够了,但我不能让它对线程起作用。
          • 是的。 ptrace 允许您等待所跟踪进程的子进程的唯一原因是 ptrace 允许您代表正在跟踪的进程执行操作。当你这样做时,当然,你需要确保不要干扰进程等待它自己的孩子;我不确定这涉及到什么。我认为跟踪您想要的所有内容并获取进程/线程已退出的跟踪通知比调用waitpid要安全得多。
          猜你喜欢
          • 1970-01-01
          • 2020-09-09
          • 2020-01-15
          • 1970-01-01
          • 1970-01-01
          • 2015-02-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多