我正在尝试使用waitpid()
来等待单个线程而不是进程。我知道pthread_join()
或std::thread::join()
是等待线程的典型方式。然而,在我的情况下,我正在开发一个监视应用程序,该应用程序分叉并执行(通过execv
)一个程序,该程序反过来会生成一些线程。所以,我无法加入监控应用程序中的线程,因为它们属于不同的进程,我无法访问源代码。不过,我希望能够等待这些单独的线程完成。
为了更容易地看到我想要实现的目标,我添加了一张图纸,希望能让它更清晰:
当我使用进程时,Everyhing工作正常,但waitpid
不等待线程。基本上,waitpid
在调用之后立即返回-1
(线程仍然在那个时间运行了几秒钟)。
waitpid
州的文档:
在Linux内核中,内核调度线程不是进程的独特构造。相反,线程只是使用Linux唯一的clone(2)系统调用创建的进程;使用clone(2)实现其他例程,例如可移植的pthread_create(3)调用。在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 question和this post)。
所有这些唤醒最终都会减慢应用程序的速度。所以我想知道我的解决方案是否存在一些缺陷以及是否有办法使其适用于Java应用程序。
答案 0 :(得分:1)
我对你声称所有过程“工作正常”的说法有点困惑。 waitpid
只能等待你自己的子进程,而不是任意的其他进程,事实上,除非它是你自己的子进程,否则几乎肯定是使用进程id的错误。
为什么不修复你的设计以使用一些适当的进程间通信机制,以便线程可以在完成时向其他进程发出信号,而不是寻找丑陋的黑客来做一些不可能的事情?或者将整个程序放在一个进程中(使用多个线程)而不是将您的工作分成多个进程和线程?
答案 1 :(得分:1)
好的,这是不是的解决方案,但解释了为什么我怀疑使用waitpid()
的解决方案:
1.1在Linux下使用clone()
创建的线程是创建它们的进程的子级。
1.2在此之后,线程是进程(A)的 grand - children,它创建了一个进程(B),而进程又创建了线程。
2 waitpid()
不触发任何已终止的子女的信号SIGCHLD
。
所有这些共同解释了为什么你的方法不起作用。
答案 2 :(得分:1)
除了线程组组长(a.k.a作为主线程)之外,你不能等待Linux中其他进程的线程。
现代Linux内核中的 sys_waitpid
被实现为sys_wait4
的包装,后者又调用do_wait
。 do_wait
完成了等待进程的繁重工作(线程只是一种特殊的进程)。它只迭代当前任务的已知子节点,如果未指定__WNOTHREAD
,则覆盖同一线程组中其他线程的子节点。
这里有趣的时刻是使用clone
系统调用创建一个线程实际上将新创建的线程的父级设置为克隆但是此父级所在的进程的父级。没有通知它刚刚获得一个新的孩子(它没有在其task
结构的列表中注册)。当克隆存在时,它也不会收到SIGCHLD
,因为线程的退出信号被-1
设置为copy_process
- 实际复制进程的函数。
这背后的基本原理非常简单:等待是单次操作 - 一旦等待执行并完成,等待过程就不再存在。如果允许另一个进程在当前进程的线程或子进程上等待,则从当前进程获取对其子进程执行等待的能力。你也创建了一个可能的竞争条件,并且肯定不会因为其他进程等待你的某个线程而失败pthread_join()
失败,是吗?
答案 3 :(得分: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,虽然线程退出检测存在延迟(取决于您的目录扫描间隔)。
答案 4 :(得分:0)
据我所知,waitpid仅用于处理指定的终止子程序。并且当有许多subpro等待一次处理时,它比等待更安全。