我正在尝试使用waitpid()
等待单个线程而不是进程。 我知道pthread_join()
或std::thread::join()
是等待线程的典型方法。 但是,在我的情况下,我正在开发一个监视应用程序,它通过execv
执行和执行一个程序,该程序反过来产生一些线程。 所以,我不能从监视应用程序中join这些线程,因为它们属于不同的进程,我无法访问源代码。 不过,我希望能够等待这些单独的线程完成。
为了更容易看到我想要实现的目标,我添加了一张图纸,希望能够更加清晰:
当我使用进程时,Everyhing可以正常工作,但waitpid
不会等待线程。 基本上, waitpid
在被调用之后立即返回-1
(线程仍然在运行几秒钟)。
waitpid
状态的文档:
在Linux内核中,内核调度的线程并不是一个独立的进程。 相反,线程只是一个使用Linux独特的克隆(2)系统调用创build的进程; 其他例程如可移植的pthread_create(3)调用是使用clone(2)来实现的。 在Linux 2.4之前,一个线程只是一个进程的特例,结果一个线程不能等待另一个线程的subprocess,即使后者属于同一个线程组。 然而,POSIX规定了这样的function,而且从Linux 2.4开始,一个线程可以在默认情况下等待同一个线程组中的其他线程的subprocess。
这个描述只考虑从一个线程等待其他线程的子线程(在我的情况下,我想等待另一个线程的子线程)。 但是,至less,它显示了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
等待一个线程。 还有其他的标志我需要使用吗? 你能指出我的任何文件,在那里解释如何等待另一个进程的线程?
作为参考,我用来创build线程的代码是:
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是进程并且它们是线程时,这个代码都可以工作。
不过,我有一个问题。 如果我使用multithreadingC ++应用程序尝试此监视工具,一切工作正常。 但最初的意图是使用这个监视工具与产生多个线程的Java应用程序。 当使用multithreadingJava应用程序时,循环中的waitpid
每秒唤醒很多次(子线程被一个SIGSEGV信号停止)。 这似乎与Java为了自己的目的使用SIGSEGV这一事实有关(参见这个问题和这篇文章 )。
所有这些唤醒最终会使应用程序变慢。 所以,我想知道我的解决scheme是否存在一些缺陷,以及是否有办法使它与Java应用程序一起工作。
我对你所声称的一切“对流程来说工作正常”感到困惑。 waitpid
只能等待你自己的子进程,而不是任意的其他进程,实际上它几乎肯定是一个使用进程id的错误,除非它是你自己的子进程。
而不是寻找丑陋的黑客做一些不可能的事情,为什么不修改你的设计来使用一些适当的进程间通信机制,以便线程在完成时可以向另一个进程发信号? 或者把整个程序放在一个单独的进程中(多线程),而不是把你的工作分成多个进程和线程?
好吧,这不是一个解决方案,但解释为什么我怀疑有使用waitpid()
的解决方案:
1.1在Linux下,使用clone()
创建的线程是创建它们的进程的子进程。
1.2在此之后,线程是进程(A)的祖先 – 子进程(B),进程(B)又创建线程。
2 waitpid()
不触发任何终止的子孙的信号SIGCHLD
。
所有这一切解释了为什么你的方法不起作用。
除了线程组领导者(也就是主线程)之外,您不能在Linux中的其他进程中等待线程。
现代Linux内核中的sys_waitpid
是作为sys_wait4
的包装器来sys_wait4
,后者又调用do_wait
。 do_wait
在等待进程上do_wait
(线程只是特殊的进程)。 它只遍历当前任务的已知子__WNOTHREAD
,并且如果__WNOTHREAD
指定__WNOTHREAD
,则会__WNOTHREAD
同一个线程组中其他线程的子__WNOTHREAD
。
有趣的是,使用clone
系统调用创建线程实际上将新创建的线程的父项设置为被克隆进程的父项, 但是并没有通知该父项已经获得了新的子项(这是未在task
结构的列表中注册)。 因为线程的退出信号被copy_process
设置为-1
(实际上复制进程的函数),所以当克隆存在时它也不会收到SIGCHLD
。
这背后的基本原理很简单:等待是一个单一的操作 – 一旦等待完成,等待的过程不再存在。 如果允许另一个进程在当前进程的线程或子进程中等待,则从当前进程中获取对其子进程执行等待的能力。 你也创建一个可能的竞争条件,肯定不会享受pthread_join()
失败,因为一些其他进程已经等待在你的一个线程,你会?
在Linux中,可以监视/proc/PID/task/
目录,其中包含属于进程PID的每个线程的目录。
不幸的是,inotify接口在这里似乎没有帮助,所以你必须反复扫描/proc/PID/task/
目录中的线程ID。 幸运的是,这似乎是最低的成本,特别是如果你只扫描一打或最多每秒几十次。 请注意,当线程退出时,目录将消失,而不是线程收获时。
TID == PID的一个线程是Linux中的原始进程。 其他线程将按照递增顺序获得TID(当然,尽管它们将最终环绕)。 请注意,TID与pthread线程无关。 为了找出哪个TID将映射到哪个pthread_t,正在运行的线程必须调用gettid()
(实际上是syscall(SYS_gettid)
); 否则很难判断哪个线程是基于TID或/proc/PID/task/TID/
内容的。 如果你只对线程转换感兴趣(如果/创建和/或退出),那么这个接口比ptrace更有效率,尽管线程退出检测有一个延迟 (这取决于你的目录扫描间隔) 。
据我所知,waitpid只用于处理指定的终止子程序。 当等待处理的子流很多时,比等待更安全。