c++ 多线程_如何调试多线程程序

当然,多线程调试的前提是你需要熟悉多线程的基础知识,包括线程的创建和退出、线程之间的各种同步原语等。如果您还不熟悉多线程编程的内容,可以参考这个专栏《C++ 多线程编程专栏》,如果您不熟悉 gdb 调试可以参考这个专栏《Linux GDB 调试教程》。

一、调试多线程的方法

使用 gdb 将程序跑起来,然后按 Ctrl + C 将程序中断下来,使用 info threads 命令查看当前进程有多少线程。

79d366e0b28b3bdf24b6f73163519ca0.png

还是以 redis-server 为例,当使用 gdb 将程序运行起来后,我们按 Ctrl + C 将程序中断下来,此时可以使用 info threads 命令查看 redis-server 有多少线程,每个线程正在执行哪里的代码。

使用 thread 线程编号 可以切换到对应的线程去,然后使用 bt 命令可以查看对应线程从顶到底层的函数调用,以及上层调用下层对应的源码中的位置;当然,你也可以使用 frame 栈函数编号 (栈函数编号即下图中的 #0 ~ #4,使用 frame 命令时不需要加 #)切换到当前函数调用堆栈的任何一层函数调用中去,然后分析该函数执行逻辑,使用 print 等命令输出各种变量和表达式值,或者进行单步调试。

8c399fc1668e0fe49cb372b3695d183c.png

如上图所示,我们切换到了 redis-server 的 1 号线程,然后输入 bt 命令查看该线程的调用堆栈,发现顶层是 main 函数,说明这是主线程,同时得到从 main 开始往下各个函数调用对应的源码位置,我们可以通过这些源码位置来学习研究调用处的逻辑。 对每个线程都进行这样的分析之后,我们基本上就可以搞清楚整个程序运行中的执行逻辑了。

接着我们分别通过得到的各个线程的线程函数名去源码中搜索,找到创建这些线程的函数(下文为了叙述方便,以 f 代称这个函数),再接着通过搜索 f 或者给 f 加断点重启程序看函数 f 是如何被调用的,这些操作一般在程序初始化阶段。

redis-server 1 号线线程是在 main 函数中创建的,我们再看下 2 号线程的创建,使用 thread 2 切换到 2号线程,然后使用 bt 命令查看 2 号线程的调用堆栈,得到 2 号线程的线程函数为 bioProcessBackgroundJobs ,注意在顶层的 clone 和 start_thread 是系统函数,我们找的线程函数应该是项目中的自定义线程函数。

1a8041cdbcf6a5a511395a15a4ddce64.png

通过在项目中搜索 bioProcessBackgroundJobs 函数,我们发现 bioProcessBackgroundJobs 函数在 bioInit 中被调用,而且确实是在 bioInit 函数中创建了线程 2,因此我们看到了 pthread_create(&thread,&attr,bioProcessBackgroundJobs,arg) != 0) 这样的调用。

 1//bio.c 96行 2void bioInit(void) { 3    //...省略部分代码... 4 5    for (j = 0; j < BIO_NUM_OPS; j++) { 6        void *arg = (void*)(unsigned long) j; 7        //在这里创建了线程 bioProcessBackgroundJobs 8        if (pthread_create(&thread,&attr,bioProcessBackgroundJobs,arg) != 0) { 9            serverLog(LL_WARNING,"Fatal: Can't initialize Background Jobs.");10            exit(1);11        }12        bio_threads[j] = thread;13    }14}

此时,我们可以继续在项目中查找 bioInit 函数,看看它在哪里被调用的,或者直接给 bioInit 函数加上断点,然后重启 redis-server,等断点触发,使用 bt 命令查看此时的调用堆栈就知道 bioInit 函数在何处调用的了。

 1(gdb) b bioInit 2Breakpoint 1 at 0x498e5e: file bio.c, line 103. 3(gdb) r 4The program being debugged has been started already. 5Start it from the beginning? (y or n) y 6Starting program: /root/redis-6.0.3/src/redis-server  7[Thread debugging using libthread_db enabled] 8//...省略部分无关输出... 9Breakpoint 1, bioInit () at bio.c:10310103         for (j = 0; j < BIO_NUM_OPS; j++) {11(gdb) bt12#0  bioInit () at bio.c:10313#1  0x0000000000431b5d in InitServerLast () at server.c:295314#2  0x000000000043724f in main (argc=1, argv=0x7fffffffe318) at server.c:514215(gdb) 

至此我们发现 2 号线程是在 main 函数中调用了 InitServerLast 函数,后者又调用 bioInit 函数,然后在 bioInit 函数中创建了新的线程 bioProcessBackgroun

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值