Linux主线程与子线程关系

问题背景

起因:笔者在开发一个统计Linux系统各个进程、线程loading的软件(精度高于top命令),目前是间隔500ms采样一次,在内核遍历所有线程时,发现如果有的线程在采样间隔中间退出的话,for_each_process不能遍历到该线程,导致该周期内的线程runtime丢失。为了解决该问题,在线程退出路径的最后一步do_exit()函数中,记录最后一周期该线程的runtime,理论上这样线程的runtime就不会丢失了。但实际操作中遇到了一个新的问题:do_exit中统计了该线程的运行时间,在do_exit()的do_task_dead()函数中进行了调度,该线程确实再也不会运行了,一切看起来很nice,但是其task_struct并没有在第一时间被回收(原因未明确),导致在for_each_process依然可以遍历到该进程,这就导致了该退出线程的runtime被重复记录。

笔者在开发的过程中产生了几个问题:一个进程由一个主线程与若干个子线程构成,如果主线程退出了,那么子线程的行为是什么样的呢?是退出还是继续运行?如果子线程继续运行的话,其tgid是谁?如果是父进程fork了子进程,然后父进程退出呢?做一下关于孤儿进程和僵尸进程的实验

主线程退出后子线程的状态

背景知识

return和pthread_exit的区别

main函数中return和pthread_exit的区别:return之后,子线程也会退出。pthread_exit() 函数只会终止当前线程(并不退出),不会影响进程中其它线程的执行。

#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>

void *ThreadFun(void *arg)
{
    while (1) {
        printf("child thread pid=%d\n", gettid());
        sleep(1);
    }
}
int main()
{
    printf("father thread pid=%d\n", gettid());
    int res;
    pthread_t myThread;
    res = pthread_create(&myThread, NULL, ThreadFun, NULL);
    sleep(3);
    printf("father thread reach trace point\n");
    return 0;
}

输出:

chen@ubuntu:~/Desktop/test_fork$ ./test_fork 
father thread pid=6534
child thread pid=6535
child thread pid=6535
child thread pid=6535

子线程也直接挂了,如果把return 0换成pthread_exit的话

chen@ubuntu:~/Desktop/test_fork$ ./test_fork 
father thread pid=6472
child thread pid=6473
child thread pid=6473
child thread pid=6473
father thread reach trace point
child thread pid=6473
child thread pid=6473

可以发现子线程还是在继续跑

join和detach的区别

区别在于两者是否阻塞主调线程

  • 当使用join()函数时,主调线程阻塞,等待被调线程终止,然后主调线程回收被调线程资源,并继续运行;
  • 当使用detach()函数时,主调线程继续运行,被调线程驻留后台运行,主调线程无法再取得该被调线程的控制权。当主调线程结束时,由运行时库负责清理与被调线程相关的资源;
  • 如果什么都不加,默认是join,join的位置在主函数的最下面,也就是pthread_exit()之前,相当于pthread_exit会自动等待子线程都运行完。这种情况相当于主线程、子线程可以一起并行,主线程也会等待子线程结束。

主线程pthread_join()

int main()
{
    printf("father thread pid=%d\n", gettid());
    int res;
    pthread_t myThread;
    res = pthread_create(&myThread, NULL, ThreadFun, NULL);
    sleep(3);
    printf("father thread reach trace point\n");
    pthread_join(myThread, NULL);
    pthread_exit(NULL);
    return 0;
}

输出:

chen@ubuntu:~/Desktop/test_fork$ ./test_fork 
father thread pid=6705
child thread pid=6706
child thread pid=6706
child thread pid=6706
father thread reach trace point
child thread pid=6706
child thread pid=6706

查看子线程的tgid是多少

chen@ubuntu:~/Desktop/test_fork$ readlink -f /proc/*/task/6706 | cut -d/ -f3
6705

主线程pthread_detach()

int main()
{
    printf("father thread pid=%d\n", gettid());
    int res;
    pthread_t myThread;
    res = pthread_create(&myThread, NULL, ThreadFun, NULL);
    sleep(10);
    printf("father thread reach trace point\n");
    pthread_detach(myThread);
    pthread_exit(NULL);
    return 0;
}

如果删除pthread_exit(NULL)的话,主线程和子线程会一块退出,这个不讨论
输出:

chen@ubuntu:~/Desktop/test_fork$ ./test_fork 
father thread pid=7897
child thread pid=7898
child thread pid=7898
child thread pid=7898
child thread pid=7898
child thread pid=7898
child thread pid=7898
child thread pid=7898
child thread pid=7898
child thread pid=7898
child thread pid=7898
father thread reach trace point
child thread pid=7898
child thread pid=7898

查看pid下面的tid、tgid

chen@ubuntu:~/Desktop/test_fork$ ps --pid 7897 -O tgid,tid,lwp,nlwp -L
    PID    TGID     TID     LWP NLWP S TTY          TIME COMMAND
   7897    7897    7897    7897    2 S pts/0    00:00:00 ./test_fork
   7897    7897    7898    7898    2 S pts/0    00:00:00 ./test_fork

在输出“father thread reach trace point”之后,再查看

chen@ubuntu:~/Desktop/test_fork$ ps --pid 7897 -O tgid,tid,lwp,nlwp -L
    PID    TGID     TID     LWP NLWP S TTY          TIME COMMAND
   7897    7897    7897    7897    2 Z pts/0    00:00:00 [test_fork] <defunct>
   7897    7897    7898    7898    2 S pts/0    00:00:00 ./test_fork

主线程后面多了一个<defunct>标志(僵尸进程)

如何理解这个僵尸进程呢?

  • 先说僵尸进程的定义:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵死进程
  • 那么明明7898是7897 fork出来的,怎么7897反而成了僵尸呢?这么理解:bash作为ppid创建了主线程,然后主线程创建了子线程,尽管主线程运行完了,但是子线程还在运行,所以主线程的task_struct等资源不能回收。所以主线程处在既不能回收又不能被父进程waitpid()的尴尬境地,按照定义,运行完又没被父进程处理,那这就属于僵尸进程了。

参考文章

pid、tgid、ppid的关系: https://www.baeldung.com/linux/pid-tid-ppid
return和pthread_exit的区别: http://c.biancheng.net/view/8608.html
孤儿进程、僵尸进程: https://www.cnblogs.com/anker/p/3271773.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值