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 应用程序时,waitpidin 循环每秒唤醒多次(子线程被 SIGSEGV 信号停止)。这似乎与 Java 出于自己的目的使用 SIGSEGV 的事实有关(请参阅此问题此帖子)。

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

4

5 回答 5

3

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

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

于 2012-07-02T14:16:40.630 回答
2

除了线程组领导(也称为主线程)之外,您不能在 Linux 中的其他进程中等待线程。

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

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

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

于 2012-07-02T17:45:09.577 回答
1

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

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

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

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

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

于 2012-07-02T15:34:09.400 回答
0

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

于 2015-03-23T11:53:04.143 回答
0

在 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 更有效,尽管线程退出检测存在延迟(这取决于您的目录扫描间隔) .

于 2012-07-02T20:18:21.147 回答