使用waitpid等待另一个进程的线程

3
我正在尝试使用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有关(请参见此问题此文章)。
所有这些唤醒最终导致应用程序变得非常缓慢。因此,我想知道我的解决方案是否存在一些缺陷,并且是否有办法使其与Java应用程序一起正常工作。

1
虽然我不熟悉使用waitpid()等待线程终止,但我会解释你发布的man 2 waitpid引用,以便可以在创建线程的进程之外使用waitpid(..., __WALL)来监视所需的子进程(主线程的子进程)。由于waitpid()只等待子进程而不是孙子进程,因此我认为你走错了路。 - alk
@alk 是的,你可能是对的。然而,在waitpid手册页中,它指出以下Linux特定选项用于使用clone(2)创建的子进程...(指的是__WALL等)。因此,某种程度上似乎应该可以等待线程。无论如何,我会继续寻找解决方案,同时希望有人已经在这方面做过,并发布了解决方案 :) - betabandido
为什么不尝试我概述的/proc/PID/task/TID/扫描方法呢?它只适用于Linux系统,而且你无法获得通知,但是扫描/proc/PID/task/非常轻量级。你需要一个例子吗? - Nominal Animal
5个回答

3
我对你声称所有进程“正常工作”的说法有些困惑。`waitpid` 只能等待您自己的子进程,而不是任意其他进程。实际上,除了在处理自己的子进程时使用进程 ID 之外,几乎肯定会出现错误。
与其寻找丑陋的解决方案来实现本不应该可能的事情,为什么不修复设计,使用一些适当的进程间通信机制,以便线程可以在完成工作时向其他进程发出信号?或者将整个程序放在单个进程中(具有多个线程),而不是将工作分散到多个进程和线程中?

我更新了问题并添加了一个图表,希望能更清楚地表达我的观点。正如您所看到的,我的监控应用程序执行另一个程序。该程序然后创建线程,我想等待它们。我无法修改该程序,因此我不能做您建议的事情(我同意这将是正确的做法)。如果我监视生成进程的程序而不是生成线程的程序,则 waitpid 可以工作(即等待子进程完成)。 - betabandido
那么,听起来你想使用ptrace,但这将会有相当大的运行时开销。 - R.. GitHub STOP HELPING ICE
我只使用过一两次 ptrace,所以对它并不是很熟悉。您知道在线程创建/结束期间是否仅会发生运行时开销,还是会影响所有线程的总执行时间? - betabandido
实际上,我过去使用ptrace来附加到在我的监控软件启动之前在系统中运行的进程。通过这种方式,我可以等待它们完成。如果我需要等待作为我的监控应用程序的“孙子”进程/线程,我是否也需要使用ptrace?对于进程,似乎waitpid就足够了,但我无法使其适用于线程。 - betabandido
是的。ptrace 允许您等待跟踪进程的子进程,这仅是因为 ptrace 允许您代表您正在跟踪的进程执行操作。当您这样做时,当然需要确保不干扰该进程等待自己的子进程;我不确定其中涉及了什么。我认为,跟踪*您想要的所有内容,并获取有关进程/线程已退出的跟踪通知比调用waitpid更安全。 - R.. GitHub STOP HELPING ICE

3

在Linux中,除了在线程组领导者(也称为主线程)上,您无法等待其他进程中的线程。

在现代Linux内核中,sys_waitpid被实现为sys_wait4的包装器,后者调用do_waitdo_wait对等待进程(线程只是特殊类型的进程)进行重负载处理。它仅迭代当前任务的已知子项,如果未指定__WNOTHREAD,则还会迭代同一线程组中其他线程的子项。

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

背后的理论非常简单:等待是一次性操作-一旦执行完等待操作,等待的进程就不存在了。如果允许另一个进程等待当前进程的线程或子进程,则从当前进程中获取其对子进程执行等待的能力。您还会创建可能的竞争条件,并肯定不希望pthread_join()失败,因为其他进程已经等待您的某些线程,对吗?


1

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

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

1.2 因此,线程是由创建进程(B)的进程(A)的子进程创建的。

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

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


0
据我所知,waitpid仅用于处理指定已终止的子进程。当有许多子进程需要一次性等待处理时,waitpid比wait更安全。

0

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

不幸的是,inotify接口似乎在这里没有帮助,因此您需要重复扫描/proc/PID/task/目录以获取线程ID。幸运的是,这似乎是最小的成本,特别是如果您每秒只进行一打或者几打次扫描。请注意,该目录将在线程退出时而非被回收时消失。

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


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