问题的解决及使用GDB调试多线程多进程的手段(九)

来源:互联网 发布:大数据学的是什么 编辑:程序博客网 时间:2024/05/29 04:20

NOTE : 上一篇理论请戳这里戳我

一:待解决问题

[root@localhost dhuang]# ./12_1thread started...parent about to fork...preparing locks...parent unlocking locks...parent returned from forkchild unlocking locks...child returned from fork[root@localhost dhuang]# ./12_1 >temp.txt[root@localhost dhuang]# cat temp.txtthread started...parent about to fork...preparing locks...parent unlocking locks...parent returned from forkthread started...parent about to fork...preparing locks...child unlocking locks...child returned from fork

二:分析问题

  可以看到输出到文件的话,有些信息打印了两次。笔者一下子就想到了以前笔者深度总结Standard IO的结论了:这是STANDARD IO中lined buffer和full buffer的锅,当然帮凶是fork对父进程资源继承的机制。其实问题到这里就解决了,但是这是笔者从原理上分析的结论,必然需要用实验去证实。
  为此笔者对做一下修改:1. 将标准输出指向文件,方便GDB调式查看结果。 2. 简化代码逻辑,能够说明本文目的即可。 3. 本文的重点将是如何分析问题并解决问题,而非问题本身。

三:解决问题

3.1 修改代码看看结果

[root@localhost dhuang]# vim 12_1.c                  #include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <err.h>#include <fcntl.h>#include <unistd.h>#include <string.h>void prepare(void){        printf("preparing locks...\n");}void * thr_fn(void *arg){        printf("thread started...\n");        fflush(1);//这里专门针对DeBUG        pause();        return(0);}int main(void){        int err;        pid_t pid;        pthread_t tid;        int fd;        char * buffer="hi, it's a flag to control flow\n";        //可以使用多种手段重定向标准输入到文件        close(1);        if((fd=open("data.txt",O_RDWR|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0)                errx(1,"error in creating a file\n");        //这里已经将pthread_atfork本来的目的忽略了!        if ((err = pthread_atfork(prepare, NULL, NULL)) != 0)                errx(1, "can’t install fork handlers");        if ((err = pthread_create(&tid, NULL, thr_fn, 0)) != 0)                errx(1, "can’t create thread");        sleep(2);        //这里将会更直观的看到一些区别        write(STDOUT_FILENO,buffer,strlen(buffer));        printf("parent about to fork....\n");        if ((pid = fork()) < 0)                errx(1,"fork failed");        else if (pid == 0)                printf("child returned from fork\n");        else                printf("parent returned from fork\n");        exit(0);}

  运行结果如下:

[root@localhost dhuang]# gcc -pthread -o 12_1 12_1.c[root@localhost dhuang]# ./12_1[root@localhost dhuang]# cat data.txtthread started...hi, it`s a flag to control flowparent about to fork....preparing locks...parent returned from forkparent about to fork....preparing locks...child returned from fork

  可以看到thread started...hi,it's...,parent about to fork...输出了两次,所以到这里已经很明确了,笔者通过修改的代码验证了最上面的理论分析。但是还是不够,没有做GDB的跟踪,因为GDB值得好好掌握啊,这是个练手的机会。

3.2 多线程及多进程GDB跟踪

  首先重新编译增加调试信息,本程序正好fork前跟踪另一个线程fork后跟踪另一个进程

3.2.1 跟踪线程:
[root@localhost dhuang]# gcc -pthread -o 12_1 12_1.c -g[root@localhost dhuang]# gdbGNU gdb (GDB) Fedora 8.0.1-33.fc27Copyright (C) 2017 Free Software Foundation, Inc............//简化一些信息,笔者没有用截图。(gdb) file 12_1Reading symbols from 12_1...done.(gdb) break 14Breakpoint 1 at 0x4008f4: file 12_1.c, line 14.(gdb) break mainBreakpoint 2 at 0x400921: file 12_1.c, line 25.(gdb) rStarting program: /root/dhuang/12_1Missing separate debuginfos, use: dnf debuginfo-install glibc-2.26-20.fc27.x86_64[Thread debugging using libthread_db enabled]Using host libthread_db library "/lib64/libthread_db.so.1".Breakpoint 2, main () at 12_1.c:2525              char * buffer="hi, it's a flag to control flow\n";(gdb) set scheduler-locking on(gdb) cContinuing.[New Thread 0x7ffff77d2700 (LWP 8431)][Switching to Thread 0x7ffff77d2700 (LWP 8431)]Thread 2 "12_1" hit Breakpoint 1, thr_fn (arg=0x0) at 12_1.c:1414              printf("thread started...\n");(gdb) info thread  Id   Target Id         Frame  1    Thread 0x7ffff7fd5740 (LWP 8427) "12_1" 0x00007ffff78aead0 in nanosleep () from /lib64/libc.so.6* 2    Thread 0x7ffff77d2700 (LWP 8431) "12_1" thr_fn (arg=0x0) at 12_1.c:14(gdb) next15              fflush(stdout);//这里专门针对DeBUG(gdb)//-------------------------------------------------------------------//此时在另一个终端看看data.txt文件的结果,以上fflush语句还未执行[root@localhost dhuang]# cat data.txt//什么都没有
(gdb) next16              pause();(gdb)//-----------------------------------------------------------------//此时再次在另一个终端观察结果,发现文件里面有了输出。所以因为fflush的缘故,刷新了标准输出。[root@localhost dhuang]# cat data.txtthread started...[root@localhost dhuang]#
//笔者强调下GDB没有退出过,所以还是接着上面的操作来的(gdb) thread 1[Switching to thread 1 (Thread 0x7ffff7fd5740 (LWP 8427))]#0  0x00007ffff78aead0 in nanosleep () from /lib64/libc.so.6(gdb) break 36Breakpoint 3 at 0x4009dc: file 12_1.c, line 36.(gdb) cContinuing.Thread 1 "12_1" hit Breakpoint 3, main () at 12_1.c:3636              write(STDOUT_FILENO,buffer,strlen(buffer));(gdb) next37              printf("parent about to fork....\n");(gdb) next38              if ((pid = fork()) < 0)(gdb)//-----------------------------------------------------------------//在另一个终端看看结果[root@localhost dhuang]# cat data.txtthread started...hi, it's a flag to control flow

  以上跟踪调试的结果很好的和笔者的分析吻合,因为头一个输出使用了fflush刷新缓冲区,另一个输出使用了低层次的I/O write。其实到这里本文已经可以结尾了,但是多进程的部分做个最后的验证吧。

3.2.2 跟踪进程:
//笔者强调下GDB没有退出过,所以还是接着上面的操作来的(gdb) set follow-fork-mode child(gdb) set detach-on-fork off(gdb) break 41Breakpoint 4 at 0x400a2e: file 12_1.c, line 41.(gdb) cContinuing.[New process 8512]Reading symbols from /root/dhuang/12_1...done.[Thread debugging using libthread_db enabled]Using host libthread_db library "/lib64/libthread_db.so.1".[Switching to Thread 0x7ffff7fd5740 (LWP 8512)]Thread 2.1 "12_1" hit Breakpoint 4, main () at 12_1.c:4141                      printf("child returned from fork\n");Missing separate debuginfos, use: dnf debuginfo-install glibc-2.26-20.fc27.x86_64(gdb) info inferiors  Num  Description       Executable          1    process 8427      /root/dhuang/12_1* 2    process 8512      /root/dhuang/12_1(gdb) next44              exit(0);(gdb) next[Inferior 2 (process 8512) exited normally](gdb)//-----------------------------------------------------------------//注意这里让子进程结束了[root@localhost dhuang]# cat data.txtthread started...hi, it`s a flag to control flowparent about to fork....preparing locks...child returned from fork[root@localhost dhuang]#//子进程因为exit(0),所以刷新了缓冲区,这里就可以看到子进程的输出了。注意`parent about to fork....`和`preparing locks...`
(gdb) break 43Breakpoint 5 at 0x400a3a: /root/dhuang/12_1.c:43. (2 locations)(gdb) cContinuing.Thread 1 "12_1" hit Breakpoint 5, main () at 12_1.c:4343                      printf("parent returned from fork\n");(gdb) next44              exit(0);(gdb) next[Thread 0x7ffff77d2700 (LWP 8431) exited][Inferior 1 (process 8427) exited normally](gdb) q[root@localhost dhuang]#//-----------------------------------------------------------------//注意这里让子进程结束了[root@localhost dhuang]# cat data.txtthread started...hi, it`s a flag to control flowparent about to fork....preparing locks...child returned from forkparent about to fork....preparing locks...parent returned from fork[root@localhost dhuang]#  

  至此,整个过程完全复现。所以重复下造成题目刚开始输出差别的根本原因是:

  • 标准IO函数会有默认buffer的存在,输出到文件的话是full buffer,而输出到终端的话是line buffer
  • fork子进程会继承大部分父进程的资源,其中就包括打开的文件描述符,还有标准IO 函数所分配的buffer区域!

四:写在最后

  本文笔者没有介绍GDB调试多线程和多进程的用法,本文的重点在于如何分析解决一个问题。而非任意工具的使用,本文程序也并非有实用性,笔者根据问题修改的代码。如pthread_atfork本来的目的是为了锁在多线程中fork的一致性,这里没有体现这一作用。如果对于笔者以上论述有任何疑问,请及时指正。

NOTE:GDB笔者现学现卖,如果有使用过KEIL调式单片机程序的话,完全就是一样的思路。但是多线程和多进程的调试,首先需要有相应的理论知识。

阅读全文
'); })();
1 1
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 输入一个正整数r(0 全排列 office激活 office2013激活 g_tk C#网络通信 C语言笔试 ucos qt包含文件 科大讯飞java 1,定义一个接口Assaultable(可攻击的),该接口有一个抽象方法attack()。2,定义一 FlyAudioVoice 2023 编写一个应用程序绘制一个如下的操作菜单并实现功能(定义一个类学生表示学生,有成员变量姓名name和年 Minecraft 1204:剔除相关数 平坦衰落信道2FSK差错性能分析 平坦衰落信道 编写一个应用程序绘制一个如下的操作菜单并实现功能(定义一个类学生表示学生,有成员变量姓名name和年 手机是现在人们必不可少、最重要的通讯工具,拥有一款简单、实用、易用的手机通讯录,将会使你的手机更加好 手机是现在人们必不可少、最重要的通讯工具,拥有一款简单、实用、易用的手机通讯录,将会使你的手机更加好 编写一个应用程序绘制一个如下的操作菜单并实现功能(定义一个类学生表示学生,有成员变量姓名name和年 61858 61850 全国市级城市拼音 pycr rpyc pry 安德地产公司 安德地产年报 pycharm安装 高文 李皓 王煊 赵瀚 夜的命名术 我的治愈系游戏 全职艺术家 星门 长夜余火 不科学御兽