启动 GDB 调试

2023-11-08

使用 GDB 调试程序一般有三种方式:

  • gdb filename
  • gdb attach pid
  • gdb filename corename

这也对应着本节课的核心内容:

  • 直接调试目标程序
  • 附加进程
  • 调试 core 文件

接下来我们逐一讲解。

直接调试目标程序

在开发阶段或者研究别人的项目时,当编译成功生成目标二进制文件后,可以使用 gdb filename 直接启动这个程序的调试,其中 filename 是需要启动的调试程序文件名,这种方式是直接使用 GDB 启动一个程序进行调试。注意这里说的启动一个程序进行调试其实不严谨,因为实际上只是附加(attach)了一个可执行文件,并没有把程序启动起来;接着需要输入run 命令,程序才会真正的运行起来。关于 run 命令后面的课程中会详细介绍。上一课的 GDB 调试 hello_server 系列就是使用的这种方式。

假设现在有一个程序叫 fileserver,使用 gdb fileserver 附加该程序,然后使用 run 命令启动该程序。如下图所示:

enter image description here

附加进程

在某些情况下,一个程序已经启动了,我们想调试这个程序,但是又不想重启这个程序。假设有这样一个场景,我们的聊天测试服务器程序正在运行,运行一段时间之后,发现这个聊天服务器不能接受新的客户端连接了,这时肯定是不能重启程序的,如果重启,当前程序的各种状态信息就丢失了。怎么办呢?可以使用 gdb attach 进程 ID 来将 GDB 调试器附加到聊天测试服务器程序上。例如,假设聊天程序叫 chatserver,可以使用 ps 命令获取该进程的 PID,然后使用 gdb attach 就可以调试了,操作如下:

[zhangyl@iZ238vnojlyZ flamingoserver]$ ps -ef | grep chatserver
zhangyl  21462 21414  0 18:00 pts/2    00:00:00 grep --color=auto chatserver
zhangyl  26621     1  5 Oct10 ?        2-17:54:42 ./chatserver -d

复制

实际执行如下图所示:

enter image description here

通过以上代码得到 chatserver 的 PID 为 26621,然后使用 gdb attach 26621 把 GDB 附加到 chatserver 进程,操作并输出如下:

[zhangyl@localhost flamingoserver]$ gdb attach 26621
Attaching to process 26661
Reading symbols from /home/zhangyl/flamingoserver/chatserver...done.
Reading symbols from /usr/lib64/mysql/libmysqlclient.so.18...Reading symbols from /usr/lib64/mysql/libmysqlclient.so.18...(no debugging symbols found)...done.
Reading symbols from /lib64/libpthread.so.0...(no debugging symbols found)...done.
[New LWP 42931]
[New LWP 42930]
[New LWP 42929]
[New LWP 42928]
[New LWP 42927]
[New LWP 42926]
[New LWP 42925]
[New LWP 42924]
[New LWP 42922]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Loaded symbols for /lib64/libpthread.so.0
Reading symbols from /lib64/libc.so.6...(no debugging symbols found)...done.

复制

为了节约篇幅,上述代码中我删掉了一些无关的信息。当提示 “Attaching to process 26621” 时就说明我们已经成功地将 GDB 附加到目标进程了。需要注意的是,程序使用了一些系统库(如 libc.so),由于这是发行版本的 Linux 系统,这些库是没有调试符号的,因而 GDB 会提示找不到这些库的调试符号。因为目的是调试 chatserver,对系统 API 调用的内部实现并不关注,所以这些提示可以不用关注,只要 chatserver 这个文件有调试信息即可。

当用 gdb attach 上目标进程后,调试器会暂停下来,此时可以使用 continue 命令让程序继续运行,或者加上相应的断点再继续运行程序(这里提到的 continue 命令不熟悉也没有关系,后续会详细介绍这些命令的使用方法)。

当调试完程序想结束此次调试时,而且不对当前进程 chatserver 有任何影响,也就是说想让这个程序继续运行,可以在 GDB 的命令行界面输入 detach 命令让程序与 GDB 调试器分离,这样 chatserver 就可以继续运行了:

(gdb) detach
Detaching from program: /home/zhangyl/flamingoserver/chatserver, process 42921

复制

然后再退出 GDB 就可以了:

(gdb) quit
[zhangyl@localhost flamingoserver]$

复制

调试 core 文件

有时候,服务器程序运行一段时间后会突然崩溃,这并不是我们希望看到的,需要解决这个问题。只要程序在崩溃的时候有 core 文件产生,就可以使用这个 core 文件来定位崩溃的原因。当然,Linux 系统默认是不开启程序崩溃产生 core 文件这一机制的,我们可以使用 ulimit -c 命令来查看系统是否开启了这一机制。

顺便提一句,ulimit 这个命令不仅仅可以查看 core 文件生成是否开启,还可以查看其他的一些功能,比如系统允许的最大文件描述符的数量等,具体可以使用 ulimit -a 命令来查看,由于这个内容与本课主题无关,这里不再赘述。

[zhangyl@localhost flamingoserver]$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 15045
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 4096
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

复制

发现 core file size 那一行默认是 0,表示关闭生成 core 文件,可以使用“ulimit 选项名 设置值”来修改。例如,可以将 core 文件生成改成具体某个值(最大允许的字节数),这里我们使用 ulimit -c unlimitedunlimited 是 -c 选项值)直接修改成不限制大小。

[zhangyl@localhost flamingoserver]$ ulimit -c unlimited
[zhangyl@localhost flamingoserver]$ ulimit -a
core file size          (blocks, -c) unlimited
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 15045
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 4096
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

复制

注意,这个命令容易记错,第一个 ulimit 是 Linux 命令, -c 选项后面的 unlimited 是选项的值,表示不限制大小,当然也可以改成具体的数值大小。很多初学者在学习这个命令时,总是把 ulimit 命令和 unlimited 取值搞混淆,如果读者能理解其含义,一般就不会混淆了。

还有一个问题就是,这样修改以后,当我们关闭这个 Linux 会话,设置项的值就会被还原成 0,而服务器程序一般是以后台程序(守护进程)长周期运行,也就是说当前会话虽然被关闭,服务器程序仍然继续在后台运行,这样这个程序在某个时刻崩溃后,是无法产生 core 文件的,这种情形不利于排查问题。因此,我们希望这个选项永久生效,永久生效的方式是把“ulimit -c unlimited”这一行加到 /etc/profile 文件中去,放到这个文件最后一行即可。

具体的例子

生成的 core 文件的默认命名方式是 core.pid,举个例子,比如某个程序当时运行时其进程 ID 是 16663,那么它崩溃产生的 core 文件的名称就是 core.16663。我们来看一个具体的例子,某次我发现服务器上的 msg_server 崩溃了,产生了一个如下的 core 文件:

-rw------- 1 root root 10092544 Sep  9 15:14 core.21985

复制

就可以通过这个 core.21985 文件来排查崩溃的原因,调试 core 文件的命令是:

gdb filename corename

复制

其中,filename 就是程序名,这里就是 msg_server;corename 是 core.21985,我们输入 gdb msg_server core.21985 来启动调试:

[root@myaliyun msg_server]# gdb msg_server core.21985
Reading symbols from /root/teamtalkserver/src/msg_server/msg_server...done.
[New LWP 21985]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `./msg_server -d'.
Program terminated with signal 11, Segmentation fault.
#0  0x00000000004ceb1f in std::less<CMsgConn*>::operator() (this=0x2283878, __x=@0x7ffca83563a0: 0x2284430, __y=@0x51: <error reading variable>)
    at /usr/include/c++/4.8.2/bits/stl_function.h:235
235           { return __x < __y; }

复制

可以看到程序崩溃的地方是在 stl_function.h 的第 235 行,然后通过 bt 命令(后续将详细介绍该命令)查看崩溃时的调用堆栈,进一步分析就能找到崩溃的原因。

(gdb) bt
#0  0x00000000004ceb1f in std::less<CMsgConn*>::operator() (this=0x2283878, __x=@0x7ffca83563a0: 0x2284430, __y=@0x51: <error reading variable>)
    at /usr/include/c++/4.8.2/bits/stl_function.h:235
#1  0x00000000004cdd70 in std::_Rb_tree<CMsgConn*, CMsgConn*, std::_Identity<CMsgConn*>, std::less<CMsgConn*>, std::allocator<CMsgConn*> >::_M_get_insert_unique_pos
    (this=0x2283878, __k=@0x7ffca83563a0: 0x2284430) at /usr/include/c++/4.8.2/bits/stl_tree.h:1324
#2  0x00000000004cd18a in std::_Rb_tree<CMsgConn*, CMsgConn*, std::_Identity<CMsgConn*>, std::less<CMsgConn*>, std::allocator<CMsgConn*> >::_M_insert_unique<CMsgConn* const&> (this=0x2283878, __v=@0x7ffca83563a0: 0x2284430) at /usr/include/c++/4.8.2/bits/stl_tree.h:1377
#3  0x00000000004cc8bd in std::set<CMsgConn*, std::less<CMsgConn*>, std::allocator<CMsgConn*> >::insert (this=0x2283878, __x=@0x7ffca83563a0: 0x2284430)
    at /usr/include/c++/4.8.2/bits/stl_set.h:463
#4  0x00000000004cb011 in CImUser::AddUnValidateMsgConn (this=0x2283820, pMsgConn=0x2284430) at /root/teamtalkserver/src/msg_server/ImUser.h:42
#5  0x00000000004c64ae in CDBServConn::_HandleValidateResponse (this=0x227f6a0, pPdu=0x22860d0) at /root/teamtalkserver/src/msg_server/DBServConn.cpp:319
#6  0x00000000004c5e3d in CDBServConn::HandlePdu (this=0x227f6a0, pPdu=0x22860d0) at /root/teamtalkserver/src/msg_server/DBServConn.cpp:203
#7  0x00000000005022b3 in CImConn::OnRead (this=0x227f6a0) at /root/teamtalkserver/src/base/imconn.cpp:148
#8  0x0000000000501db3 in imconn_callback (callback_data=0x7f4b20 <g_db_server_conn_map>, msg=3 '\003', handle=8, pParam=0x0)
    at /root/teamtalkserver/src/base/imconn.cpp:47
#9  0x0000000000504025 in CBaseSocket::OnRead (this=0x227f820) at /root/teamtalkserver/src/base/BaseSocket.cpp:178
#10 0x0000000000502f8a in CEventDispatch::StartDispatch (this=0x2279990, wait_timeout=100) at /root/teamtalkserver/src/base/EventDispatch.cpp:386
#11 0x00000000004fddbe in netlib_eventloop (wait_timeout=100) at /root/teamtalkserver/src/base/netlib.cpp:160
#12 0x00000000004d18c2 in main (argc=2, argv=0x7ffca8359978) at /root/teamtalkserver/src/msg_server/msg_server.cpp:213
(gdb)

复制

堆栈 #4 就不是库代码了,我们可以排查这里的代码,然后找到问题原因。

自定义 core 文件名称

但是细心的读者会发现一个问题:一个正在程序运行时,其 PID 是可以获取到的,但是当程序崩溃后,产生了 core 文件,尤其是多个程序同时崩溃,我们根本没法通过 core 文件名称中的 PID 来区分到底是哪个服务解决这个问题有两个方法:

  • 程序启动时,记录一下自己的 PID
void writePid()
{
      uint32_t curPid = (uint32_t) getpid();
      FILE* f = fopen("xxserver.pid", "w");
      assert(f);
      char szPid[32];
      snprintf(szPid, sizeof(szPid), "%d", curPid);
      fwrite(szPid, strlen(szPid), 1, f);
      fclose(f);
}

复制

我们在程序启动时调用上述 writePID 函数,将程序当时的 PID 记录到 xxserver.pid 文件中去,这样当程序崩溃时,可以从这个文件中得到进程当时运行的 PID,这样就可以与默认的 core 文件名后面的 PID 做匹配了。

  • 自定义 core 文件的名称和目录

/proc/sys/kernel/core_uses_pid 可以控制产生的 core 文件的文件名中是否添加 PID 作为扩展,如果添加则文件内容为 1,否则为 0;/proc/sys/kernel/core_pattern 可以设置格式化的 core 文件保存位置或文件名。修改方式如下:

echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern

复制

各个参数的说明如下:

参数名称 参数含义(英文) 参数含义(中文)
%p insert pid into filename 添加 pid 到 core 文件名中
%u insert current uid into filename 添加当前 uid 到 core 文件名中
%g insert current gid into filename 添加当前 gid 到 core 文件名中
%s insert signal that caused the coredump into the filename 添加导致产生 core 的信号到 core 文件名中
%t insert UNIX time that the coredump occurred into filename 添加 core 文件生成时间(UNIX)到 core 文件名中
%h insert hostname where the coredump happened into filename 添加主机名到 core 文件名中
%e insert coredumping executable name into filename 添加程序名到 core 文件名中

假设现在的程序叫 test,我们设置该程序崩溃时的 core 文件名如下:

echo "/root/testcore/core-%e-%p-%t" > /proc/sys/kernel/core_pattern

复制

那么最终会在 /root/testcore/ 目录下生成的 test 的 core 文件名格式如下:

-rw-------. 1 root root 409600 Jan 14 13:54 core-test-13154-1547445291

复制

需要注意的是,您使用的用户必须对指定 core 文件目录具有写权限,否则生成时 会因为权限不足而导致无法生成 core 文件。

小结

本节课介绍了使用 GDB 调试程序的三种方式,理解并熟练使用这三种方式可以帮助读者在遇到问题时准确地选择调试方法。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

启动 GDB 调试 的相关文章

  • Qt 使用布局管理器,控件大小未能自适应变化

    问题 使用布局管理器管理子部件 使用了QVHlayout管理器 发现控件大小固定 即使通过拖动窗口也不能改变控件的大小 布局管理器不是自动控制部件的大小吗 为什么没有生效 如图所示 解决办法 对子部件添加延申策略 setSizePolicy
  • imx6ull开发板,usb免驱摄像头的配置

    在 dev下面 只能找到video0 说明开发板并没有识别出有新连接进来的摄像头 需要在内核中 配置支持UVC标准的USB驱动 重新配置即可
  • VMWare里Linux的网络配置

    今天终于把十几天前安装在VMWare里的Linux的网络配置搞定了 高兴啊 刚使用虚拟机时 就被它神奇的虚拟功能所着迷 因为一直想在电脑里装Linux 可是由于它与Windows的不兼容 怕一安装就把原来的系统破坏 但是在虚拟机里真的能很好
  • linux 多线程 pthread_cond_wait(&cond,&mutex)理解

    pthread cond wait 用于阻塞一个线程 知道有信号唤醒它 下面是一个简单的例子 我们可以从程序的运行来了解条件变量的作用 pthread cond wait c include
  • smbus电池信息读取

    smbus协议的智能电池 我们可以使用i2c的读写方式获取ic信息 也可以使用smbus协议的接口获取 编译方法 arm none linux gnueabi gcc batterygetdemo c o batterygetdemo 编译
  • 13.linux进程基础

    一 进程基础 基础概念 关于进程和线程的基本概念在操作系统中早已学过 可以概括为一下几点 根本区别 进程是操作系统资源分配的基本单位 而线程是处理器任务调度和执行的基本单位 资源开销 每个进程都有独立的代码和数据空间 程序上下文 程序之间的
  • 17.linuxGPIO应用编程

    除了LED类设备可以通过sysfs文件系统控制以外 还可以使用该虚拟文件系统控制GPIO的高低电平 输入以及中断检测 一 GPIO控制高低电平 进入目录sys class gpio下可以看到有如下文件 其中gpiochip0对应硬件的GPI
  • 15.线程同步的几种方法

    一 为什么需要线程同步 线程同步通常是出现在多线程环境下的问题 对于多个线程同时访问的共享内存中的变量 如果不进行保护 就会导致一些列数据出错问题 以下图为例 假设线程A在第一次读取变量的值为10 每次写周期会将变量A加5 理论上当线程A完
  • #if defined(__cplusplus)

    由于C 编译器需要支持函数的重载 会改变函数的名称 因此dll的导出函数通常是标准C定义的 这就使得C和C 的互相调用变得很常见 但是有时可能又会直接用C来调用 不想重新写代码 让标准C编写的dll函数定义在C和C 编译器下都能编译通过 通
  • 【Linux】list_for_each_entry用法

    参考 http blog sina com cn s blog 5e99b41e0100rxgf html http hi baidu com shiftedmind blog item 1a7c8381e6a67fa56d8119da h
  • serial消息查看指令

    cat proc tty driver serial 查看当前设备上的所有串口的接收和发送的数据
  • Qt-configure配置选项

    这个页面给出了一个简短的 当使用configure脚本或configure exe二进制构建Qt时的各种不同的可用选项 当使用默认选项构建Qt 只需如下所示的从命令行调用configure 在Linux Mac OS X和Unix平台下构建
  • 启动 GDB 调试

    使用 GDB 调试程序一般有三种方式 gdb filename gdb attach pid gdb filename corename 这也对应着本节课的核心内容 直接调试目标程序 附加进程 调试 core 文件 接下来我们逐一讲解 直接
  • glob函数的使用

    glob库函数用于Linux文件系统中路径名称的模式匹配 即查找文件系统中指定模式的路径 注意 这不是正则表达式匹配 虽然有些相似 但还是有点差别 glob函数原型 include
  • Qt中正则表达式

    TOC 不积跬步 无以至千里 Qt中正则表达式 正则表达式 regular expression 就是在一个文本中匹配子字符串的一种模式 pattern 它可以简写为 regexp 一个regexp主要应用在以下几个方面 验证 一个rege
  • printk,printf 打印调试

    includelinux kernel h define KERN EMERG lt 0 gt 紧急事件消息 系统崩溃之前提示 表示系统不可用 define KERN ALERT lt 1 gt 报告消息 表示必须立即采取措施 define
  • 【c程序】expected identifier before numeric constant错误

    在linux开发中 出现类似error expected identifier before numeric constant错误 原因 定义的enum结构体与其它处的宏定义有冲突 举例说明 在文件a h中 define TRUE 1 在文
  • 例说hg(六)———— hg branch 创建分支

    开篇 branch 分支 應該也是 Hg 最重要的技能之一 在一個多人專案的開發過程中我們有時候要開發新功能 有時候是要修正某個Bug 有時候想要測試某個特異功能能不能 work 這時候我們通常都會從主 branch 再開出一條新的 bra
  • 嵌入式开发之移植MQTT到RK3568

    目录 前言 一 下载qmqtt源码 二 编译库文件 三 移植到RK3568 3 1 移植动态库libQt5Qmqtt 四 联机测试 4 1 制作demo 4 1 1 创建demo新项目 4 1 2 添加network模块支持 4 1 3 添
  • 【Linux应用】磁盘IO读写测试工具-FIO详解

    1 FIO简介 FIO是Linux下开源的一款IOPS测试工具 主要用来对磁盘进行压力测试和性能验证 它可以产生许多线程或进程来执行用户特定类型的I O操作 通过编写作业文件 类似于k8s的yaml 或者直接命令去执行测试动作 相当于是一个

随机推荐

  • 老主板BIOS不识别nvem固态硬盘,修改BIOS添加nvme驱动

    以我的技嘉ga f2a68hm s1主板为例 提前到技嘉官网GIGABYTE 技嘉科技下载好对应的BIOS 版本 1 到主板的官方 2 输入主板型号 点击 搜索 3 下载BIOS包 正题开始了 1 准备工具 MMTOOL 2 4GB以上U盘
  • 卡方检验的基本思想是比较实际观察到的频数与期望的频数之间的差异

    卡方检验 Chi Square Test 是一种用于分析分类数据之间的关联性或独立性的统计方法 它通过比较观察到的数据与预期的数据之间的差异来判断两个或多个变量之间是否存在关联 卡方检验通常用于交叉表格 列联表 的分析 例如 研究两种分类变
  • 用java和c++写一个vpn实例的思路

    1 C 开发VPN核心模块 支持更多VPN类型 cpp PPTPContext cpp SSL CTX createPPTPContext PPTPSocket cpp class PPTPSocket public PPTPSocket
  • tolua 判断对象是否为空

    https blog csdn net baidu 39447417 article details 80001371
  • Redis BitMap结构实现签到、连续签到统计

    文章目录 一 利用BitMap结构实现签到功能 1 1 BitMap用法 1 2 代码实现签到功能 1 3 统计连续签到 1 3 1 如何得到本月到今天为止的所有签到数据 1 3 2 如何从后向前遍历每个bit位 1 3 3 代码实现 一
  • 【线代】矩阵的特征值与特征向量原理详解

    如果把矩阵看作是运动 对于运动而言 最重要的当然就是运动的速度和方向 特征值就是运动的速度 特征向量就是运动的方向 参考链接 https www zhihu com question 21874816 answer 181864044 因为
  • 简单的介绍

    最近没有怎么发博客 也不能说自己没有学习 嘿嘿嘿 自己最近在学习框架和java 也很少做ctf 然后把一些学习的产出放在了知识星球或者是github上 想需要一个东西来管理一下 因为自己太懒了 没有搭建个人博客 我还是会发一些我感觉重要的东
  • 算法训练 星际交流

    ALGO 28 星际交流 资源限制 时间限制 1 0s 内存限制 256 0MB 问题描述 人类终于登上了火星的土地并且见到了神秘的火星人 人类和火星人都无法理解对方的语言 但是我们的科学家发明了一种用数字交流的方法 这种交流方法是这样 的
  • numpy的mgrid[]和ogrid[]

    函数说明 mgrid ogrid
  • C语言中常见的一些语法概念和功能

    常用代码 程序入口 int main 函数用于定义程序的入口点 输出 使用 printf 函数可以在控制台打印输出 输入 使用 scanf 函数可以接收用户的输入 条件判断 使用 if else 语句可以根据条件执行不同的代码块 循环结构
  • python使用sqlalchemy判断数据库是否包含某张表

    代码如下 from sqlalchemy import create engine def table exists engine table name 这个函数用来判断表是否存在 with engine connect as con sq
  • NLP基础知识

    1 熟悉 Python 语言 了解一个深度学习框架 Pytorch Tensorflow 或 MXNet 2 熟悉简单的机器学习模型 如 LR SVM HMM 正则化等 3 熟悉简单的深度学习模型 如 word2vec CNN RNN 目录
  • C++之红黑树

    红黑树 1 概念 红黑树 是一种二叉搜索树 但在每个结点上增加一个存储位表示结点的颜色 可以是Red或Black 通过对任何一条从根到叶子的路径上各个结点着色方式的限制 红黑树确保没有一条路径会比其他路径长出俩倍 因而是接近平衡的 2 性质
  • (转)四元数概念及其应用

    1 采用右手坐标系 OpenGL 2 旋转次序 x gt y gt z 3 矩阵是列优先存储 1 什么是四元数 直接用数学上的定义来解释 因为我很难在现实生活中找到可以描述明白的例子 i j k 为虚数 Q w xi yj zk 其中w是实
  • 图像分类算法—KNN、SVM、BP、CNN

  • Yarn日志过大无法拉取java.lang.RuntimeException: The total log size is too large.The log size limit is 10240M

    yarn拉取日志命令 yarn logs applicationId application 1624172467753 3988 appOwner root size limit mb 1 gt application 162417246
  • 多线程竞争及解决方法

    线程是非独立的 同一个进程里线程的数据是共享的 当各个线程访问数据资源时会出现竞争状态 即 数据几乎同步会被多个线程占用 造成数据混乱 即所谓的线程不安全 解决多线程问题的方法 锁 锁的好处 确保了某段关键代码 共享数据资源 只能有一个线程
  • 树莓派3B+安装Raspbian简易教程

    刚买的树莓派3B 没过一周 树莓派4就出来了 不过算了 基本上用起来差不多 所以大家继续看吧 最近有点忙 所以就不BB了 直接上主题 下载Raspbian Raspbian是树莓派的官方系统 推荐大家使用 如果不喜欢它的话 可以自己安装Ub
  • DynamicDataSource是如何解决多数据源的事务问题?

    多数据源的真面目DynamicRoutingDataSource DynamicRoutingDataSource是什么 该类实现了DataSource接口并在内部维护了一个map 其中存放了多个真实的DataSource 而key则是不同
  • 启动 GDB 调试

    使用 GDB 调试程序一般有三种方式 gdb filename gdb attach pid gdb filename corename 这也对应着本节课的核心内容 直接调试目标程序 附加进程 调试 core 文件 接下来我们逐一讲解 直接