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

本文介绍了如何使用gdb调试多线程C++程序,包括查看线程、切换线程、理解线程调度,并演示了scheduler-locking选项在调试时的作用,帮助开发者更好地控制多线程程序的调试流程。
摘要由CSDN通过智能技术生成

在上一篇文章《使用 gdb 调试多进程程序 —— 以调试 nginx 为例》我们介绍了如何使用 gdb 调试多进程程序,这篇文章我们来介绍下如何使用 gdb 调试多线程程序,同时这个方法也是我阅读和分析一个新的 C/C++ 项目常用的方法。

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

一、调试多线程的方法

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

ca188c3b76e320e1b1118adcc31d851e.png

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

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

650b5fe4dffd0c0bd96ce897c38756f0.png

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

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

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

0106994fdd755b65cf30742aaba3690a.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  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:103
10103         for (j = 0; j 11(gdb) bt
12#0  bioInit () at bio.c:103
13#1  0x0000000000431b5d i
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值