如何調(diào)試多線程程序
在上一篇文章《使用 gdb 調(diào)試多進程程序 —— 以調(diào)試 nginx 為例》我們介紹了如何使用 gdb 調(diào)試多進程程序,這篇文章我們來介紹下如何使用 gdb 調(diào)試多線程程序,同時這個方法也是我閱讀和分析一個新的 C/C++ 項目常用的方法。
當(dāng)然,多線程調(diào)試的前提是你需要熟悉多線程的基礎(chǔ)知識,包括線程的創(chuàng)建和退出、線程之間的各種同步原語等。如果您還不熟悉多線程編程的內(nèi)容,可以參考這個專欄《C++ 多線程編程專欄》,如果您不熟悉 gdb 調(diào)試可以參考這個專欄《Linux GDB 調(diào)試教程》。
一、調(diào)試多線程的方法
使用 gdb 將程序跑起來,然后按 Ctrl + C 將程序中斷下來,使用 info threads 命令查看當(dāng)前進程有多少線程。

還是以 redis-server 為例,當(dāng)使用 gdb 將程序運行起來后,我們按 Ctrl + C 將程序中斷下來,此時可以使用
info threads 命令查看 redis-server 有多少線程,每個線程正在執(zhí)行哪里的代碼。使用 thread 線程編號 可以切換到對應(yīng)的線程去,然后使用 bt 命令可以查看對應(yīng)線程從頂?shù)降讓拥暮瘮?shù)調(diào)用,以及上層調(diào)用下層對應(yīng)的源碼中的位置;當(dāng)然,你也可以使用 frame 棧函數(shù)編號(棧函數(shù)編號即下圖中的 #0 ~ #4,使用 frame 命令時不需要加 #)切換到當(dāng)前函數(shù)調(diào)用堆棧的任何一層函數(shù)調(diào)用中去,然后分析該函數(shù)執(zhí)行邏輯,使用 print 等命令輸出各種變量和表達式值,或者進行單步調(diào)試。

如上圖所示,我們切換到了 redis-server 的 1 號線程,然后輸入 bt 命令查看該線程的調(diào)用堆棧,發(fā)現(xiàn)頂層是 main 函數(shù),說明這是主線程,同時得到從 main 開始往下各個函數(shù)調(diào)用對應(yīng)的源碼位置,我們可以通過這些源碼位置來學(xué)習(xí)研究調(diào)用處的邏輯。對每個線程都進行這樣的分析之后,我們基本上就可以搞清楚整個程序運行中的執(zhí)行邏輯了。
接著我們分別通過得到的各個線程的線程函數(shù)名去源碼中搜索,找到創(chuàng)建這些線程的函數(shù)(下文為了敘述方便,以 f 代稱這個函數(shù)),再接著通過搜索 f 或者給 f 加斷點重啟程序看函數(shù) f 是如何被調(diào)用的,這些操作一般在程序初始化階段。
redis-server 1 號線線程是在 main 函數(shù)中創(chuàng)建的,我們再看下 2 號線程的創(chuàng)建,使用 thread 2 切換到 2號線程,然后使用 bt 命令查看 2 號線程的調(diào)用堆棧,得到 2 號線程的線程函數(shù)為 bioProcessBackgroundJobs,注意在頂層的 clone 和 start_thread 是系統(tǒng)函數(shù),我們找的線程函數(shù)應(yīng)該是項目中的自定義線程函數(shù)。

bioProcessBackgroundJobs 函數(shù),我們發(fā)現(xiàn) bioProcessBackgroundJobs 函數(shù)在 bioInit 中被調(diào)用,而且確實是在 bioInit 函數(shù)中創(chuàng)建了線程 2,因此我們看到了 pthread_create(&thread,&attr,bioProcessBackgroundJobs,arg) != 0) 這樣的調(diào)用。 1//bio.c?96行
2void?bioInit(void)?{
3????//...省略部分代碼...
4
5????for?(j?=?0;?j? 6????????void?*arg?=?(void*)(unsigned?long)?j;
7????????//在這里創(chuàng)建了線程?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}
此時,我們可以繼續(xù)在項目中查找 bioInit 函數(shù),看看它在哪里被調(diào)用的,或者直接給 bioInit 函數(shù)加上斷點,然后重啟 redis-server,等斷點觸發(fā),使用 bt 命令查看此時的調(diào)用堆棧就知道 bioInit 函數(shù)在何處調(diào)用的了。
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//...省略部分無關(guān)輸出...
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?in?InitServerLast?()?at?server.c:2953
14#2??0x000000000043724f?in?main?(argc=1,?argv=0x7fffffffe318)?at?server.c:5142
15(gdb)?
至此我們發(fā)現(xiàn) 2 號線程是在 main 函數(shù)中調(diào)用了 InitServerLast 函數(shù),后者又調(diào)用 bioInit 函數(shù),然后在 bioInit 函數(shù)中創(chuàng)建了新的線程 bioProcessBackgroundJobs,我們只要分析這個執(zhí)行流就能搞清楚這個邏輯流程了。
同樣的道理,redis-server 還有 3 號和 4 號線程,我們也可以按分析 2 號線程的方式去分析 3 號和 4號,讀者可以按照這里介紹的方法。
以上就是我閱讀一個不熟悉的 C/C++ 項目常用的方法,當(dāng)然對于一些特殊的項目的源碼,你還需要去了解一下該項目的的業(yè)務(wù)內(nèi)容,否則除了技術(shù)邏輯以外,你可能需要一些業(yè)務(wù)知識才能看懂各個線程調(diào)用棧以及初始化各個線程函數(shù)過程中的業(yè)務(wù)邏輯。
二、調(diào)試時控制線程切換
在調(diào)試多線程程序時,有時候我們希望執(zhí)行流一直在某個線程執(zhí)行,而不是切換到其他線程,有辦法做到這樣嗎?
為了說明清楚這個問題,我們假設(shè)現(xiàn)在調(diào)試的程序有 5 個線程,除了主線程,其他 4 個工作線程的線程函數(shù)都是下面這樣一個函數(shù):
1void*?worker_thread_proc(void*?arg)
2{
3????while?(true)
4????{
5????????//代碼行1
6????????//代碼行2
7????????//代碼行3
8????????//代碼行4
9????????//代碼行5
10????????//代碼行6
11????????//代碼行7
12????????//代碼行8
13????????//代碼行9
14????????//代碼行10
15????????//代碼行11
16????????//代碼行12
17????????//代碼行13
18????????//代碼行14
19????????//代碼行15
20????}??
21}
為了方便表述,我們把四個工作線程分別叫做 A、B、C、D。

如上圖所示,假設(shè)某個時刻, 線程 A 的停在代碼行 3 處,線程 B、C、D 停留位置代碼行 1 ~15 任一位置,此時線程 A 是 gdb 當(dāng)前調(diào)試線程,此時我們輸入 next 命令,期望調(diào)試器跳轉(zhuǎn)到代碼行 4 處;或者輸入 util 10 命令,期望調(diào)試器跳轉(zhuǎn)到**代碼行 10 **處。但是實際情況下,如果代碼行 1、代碼行 2、代碼行 13 或者代碼行 14 處設(shè)置了斷點,gdb 再次停下來的時候,可能會停在到代碼行 1 、代碼行 2 、代碼行 13、代碼行 14 這樣的地方。
這是多線程程序的特點:當(dāng)我們從代碼行 4 處讓程序繼續(xù)運行時,線程 A 雖然會繼續(xù)往下執(zhí)行,下一次應(yīng)該在代碼行 14 處停下來,但是線程 B、C、D 也在同步運行呀,如果此時系統(tǒng)的線程調(diào)度將 CPU 時間片切換到線程 B、C 或者 D 呢?那么 gdb 最終停下來的時候,可能是線程 B、C、D ?觸發(fā)了 代碼行 1 、代碼行 2 、代碼行 13、代碼行 14 處的斷點,此時調(diào)試的線程會變?yōu)?B、C 或者 D,而此時打印相關(guān)的變量值,可能就不是我們期望的線程 A 函數(shù)中的相關(guān)變量值了。
還存在一個情況,我們單步調(diào)試線程 A 時,我們不希望線程 A 函數(shù)中的值被其他線程改變。
針對調(diào)試多線程存在的上述狀況,gdb 提供了一個在調(diào)試時將程序執(zhí)行流鎖定在當(dāng)前調(diào)試線程的命令選項——scheduler-locking 選項,這個選項有三個值,分別是 on、step 和 off,使用方法如下:
1set?scheduler-locking?on/step/off
set scheduler-locking on 可以用來鎖定當(dāng)前線程,只觀察這個線程的運行情況, 當(dāng)鎖定這個線程時, 其他線程就處于了暫停狀態(tài),也就是說你在當(dāng)前線程執(zhí)行 next、step、until、finish、return 命令時,其他線程是不會運行的。
需要注意的是,你在使用 set scheduler-locking on/step 選項時要確認(rèn)下當(dāng)前線程是否是你期望鎖定的線程,如果不是,可以使用 thread + 線程編號 切換到你需要的線程再調(diào)用 set scheduler-locking on/step 進行鎖定。
set scheduler-locking step 也是用來鎖定當(dāng)前線程,當(dāng)且僅當(dāng)使用 next 或 step 命令做單步調(diào)試時會鎖定當(dāng)前線程,如果你使用 until、finish、return 等線程內(nèi)調(diào)試命令,但是它們不是單步命令,所以其他線程還是有機會運行的。相比較 on 選項值,step 選項值給為單步調(diào)試提供了更加精細(xì)化的控制,因為通常我們只希望在單步調(diào)試時,不希望其他線程對當(dāng)前調(diào)試的各個變量值造成影響。
set scheduler-locking off 用于關(guān)閉鎖定當(dāng)前線程。
我們以一個小的示例來說明這三個選項的使用吧。編寫如下代碼:
101?#include?
202?#include?
303?#include?
404?
505?long?g?=?0;
606?
707?void*?worker_thread_1(void*?p)
808?{
909????while?(true)
1010??????{
1111??????????g?=?100;
1212??????????printf("worker_thread_1\n");
1313??????????usleep(300000);
1414??????}
1515
1616??????return?NULL;
1717?}
1818
1919?void*?worker_thread_2(void*?p)
2020?{
2121?????while?(true)
2222??????{
2323?????????g?=?-100;
2424?????????printf("worker_thread_2\n");
2525?????????usleep(500000);
2626?????}
2727
2828?????return?NULL;
2929?}
3030
3131?int?main()
3232?{
3333?????pthread_t?thread_id_1;
3434?????pthread_create(&thread_id_1,?NULL,?worker_thread_1,?NULL);?
3535?????pthread_t?thread_id_2;
3636?????pthread_create(&thread_id_2,?NULL,?worker_thread_2,?NULL);??
3737
3838?????while?(true)
3939?????{
4040?????????g?=?-1;
4142?????????printf("g=%d\n",?g);
4242?????????g?=?-2;
4343?????????printf("g=%d\n",?g);
4444?????????g?=?-3;
4545?????????printf("g=%d\n",?g);
4646?????????g?=?-4;
4747?????????printf("g=%d\n",?g);
4848
4949?????????usleep(1000000);
5050??????}
5151
5252?????return?0;
5353?}
上述代碼在主線程(main 函數(shù)所在的線程)中創(chuàng)建了了兩個工作線程,主線程接下來的邏輯是在一個循環(huán)里面依次將全局變量 g 修改成 -1、-2、-3、-4,然后休眠 1 秒;工作線程 worker_thread_1、worker_thread_2 在分別在自己的循環(huán)里面將全局變量 g 修改成 100 和 -100。
我們編譯程序后將程序使用 gdb 跑起來,三個線程同時運行,交錯輸出:
1[root@myaliyun?xx]#?g++?-g?-o?main?main.cpp?-lpthread
2[root@myaliyun?xx]#?gdb?main
3...省略部分無關(guān)輸出...
4Reading?symbols?from?main...
5(gdb)?r
6Starting?program:?/root/xx/main?
7[Thread?debugging?using?libthread_db?enabled]
8...省略部分無關(guān)輸出...
9[New?Thread?0x7ffff6f56700?(LWP?402)]
10worker_thread_1
11[New?Thread?0x7ffff6755700?(LWP?403)]
12g=-1
13g=-2
14g=-3
15g=-4
16worker_thread_2
17worker_thread_1
18worker_thread_2
19worker_thread_1
20worker_thread_1
21g=-1
22g=-2
23g=-3
24g=-4
25worker_thread_2
26worker_thread_1
27worker_thread_1
28worker_thread_2
29worker_thread_1
30g=-1
31g=-2
32g=-3
33g=-4
34worker_thread_2
35worker_thread_1
36worker_thread_1
37worker_thread_2
我們按 Ctrl + C 將程序中斷下來,如果當(dāng)前線程不在主線程,可以先使用 info threads 和 thread id切換到主線程:
1^C
2Thread?1?"main"?received?signal?SIGINT,?Interrupt.
30x00007ffff701bfad?in?nanosleep?()?from?/usr/lib64/libc.so.6
4(gdb)?info?threads
5??Id???Target?Id???????????????????????????????Frame?
6*?1????Thread?0x7ffff7feb740?(LWP?1191)?"main"?0x00007ffff701bfad?in?nanosleep?()?from?/usr/lib64/libc.so.6
7??2????Thread?0x7ffff6f56700?(LWP?1195)?"main"?0x00007ffff701bfad?in?nanosleep?()?from?/usr/lib64/libc.so.6
8??3????Thread?0x7ffff6755700?(LWP?1196)?"main"?0x00007ffff701bfad?in?nanosleep?()?from?/usr/lib64/libc.so.6
9(gdb)?thread?1
10[Switching?to?thread?1?(Thread?0x7ffff7feb740?(LWP?1191))]
11#0??0x00007ffff701bfad?in?nanosleep?()?from?/usr/lib64/libc.so.6
12(gdb)?
然后在代碼 11 行和 41 行各加一個斷點。我們反復(fù)執(zhí)行 until 48 命令,發(fā)現(xiàn)工作線程 1 和 2 還是有機會被執(zhí)行的。
1(gdb)?b?main.cpp:41
2Breakpoint?1?at?0x401205:?file?main.cpp,?line?41.
3(gdb)?b?main.cpp:11
4Breakpoint?2?at?0x40116e:?file?main.cpp,?line?11.
5(gdb)?until?48
60x00007ffff704c884?in?usleep?()?from?/usr/lib64/libc.so.6
7(gdb)?
8worker_thread_2
9[Switching?to?Thread?0x7ffff6f56700?(LWP?1195)]
10
11Thread?2?"main"?hit?Breakpoint?2,?worker_thread_1?(p=0x0)?at?main.cpp:11
1211??????????????????????g?=?100;
13(gdb)?
14worker_thread_2
15[Switching?to?Thread?0x7ffff7feb740?(LWP?1191)]
16
17Thread?1?"main"?hit?Breakpoint?1,?main?()?at?main.cpp:41
1841??????????????????????printf("g=%d\n",?g);
19(gdb)?
20worker_thread_1
21worker_thread_2
22g=-1
23g=-2
24g=-3
25g=-4
26main?()?at?main.cpp:49
2749??????????????????????usleep(1000000);
28(gdb)?
29worker_thread_2
30[Switching?to?Thread?0x7ffff6f56700?(LWP?1195)]
31
32Thread?2?"main"?hit?Breakpoint?2,?worker_thread_1?(p=0x0)?at?main.cpp:11
3311??????????????????????g?=?100;
34(gdb)?
現(xiàn)在我們再次將線程切換到主線程(如果 gdb 中斷后當(dāng)前線程不是主線程的話),執(zhí)行 set scheduler-locking on 命令,然后繼續(xù)反復(fù)執(zhí)行 until 48 命令。
1(gdb)?set?scheduler-locking?on?
2(gdb)?until?48
3
4Thread?1?"main"?hit?Breakpoint?1,?main?()?at?main.cpp:41
541??????????????????????printf("g=%d\n",?g);
6(gdb)?until?48
7g=-1
8g=-2
9g=-3
10g=-4
11main?()?at?main.cpp:49
1249??????????????????????usleep(1000000);
13(gdb)?until?48
14
15Thread?1?"main"?hit?Breakpoint?1,?main?()?at?main.cpp:41
1641??????????????????????printf("g=%d\n",?g);
17(gdb)?
18g=-1
19g=-2
20g=-3
21g=-4
22main?()?at?main.cpp:49
2349??????????????????????usleep(1000000);
24(gdb)?until?48
25
26Thread?1?"main"?hit?Breakpoint?1,?main?()?at?main.cpp:41
2741??????????????????????printf("g=%d\n",?g);
28(gdb)?
29g=-1
30g=-2
31g=-3
32g=-4
33main?()?at?main.cpp:49
3449??????????????????????usleep(1000000);
35(gdb)?until?48
36
37Thread?1?"main"?hit?Breakpoint?1,?main?()?at?main.cpp:41
3841??????????????????????printf("g=%d\n",?g);
39(gdb)
我們再次使用 until 命令時,gdb 鎖定了主線程,其他兩個工作線程再也不會被執(zhí)行了,因此兩個工作線程無任何輸出。
我們再使用 set scheduler-locking step 模式再來鎖定一下主線程,然后再次反復(fù)執(zhí)行 until 48 命令。
1(gdb)?set?scheduler-locking?step
2(gdb)?until?48
3worker_thread_2
4worker_thread_1
5g=-100
6g=-2
7g=-3
8g=-4
9main?()?at?main.cpp:49
1049??????????????????????usleep(1000000);
11(gdb)?until?48
12worker_thread_2
13[Switching?to?Thread?0x7ffff6f56700?(LWP?1195)]
14
15Thread?2?"main"?hit?Breakpoint?2,?worker_thread_1?(p=0x0)?at?main.cpp:11
1611??????????????????????g?=?100;
17(gdb)?until?48
18worker_thread_2
19worker_thread_1
20
21Thread?2?"main"?hit?Breakpoint?2,?worker_thread_1?(p=0x0)?at?main.cpp:11
2211??????????????????????g?=?100;
23(gdb)?until?48
24worker_thread_2
25[Switching?to?Thread?0x7ffff7feb740?(LWP?1191)]
26
27Thread?1?"main"?hit?Breakpoint?1,?main?()?at?main.cpp:41
2841??????????????????????printf("g=%d\n",?g);
29(gdb)?until?48
30worker_thread_1
31worker_thread_2
32g=-100
33g=-2
34g=-3
35g=-4
36main?()?at?main.cpp:49
3749??????????????????????usleep(1000000);
38(gdb)?until?48
39worker_thread_2
40[Switching?to?Thread?0x7ffff6f56700?(LWP?1195)]
41
42Thread?2?"main"?hit?Breakpoint?2,?worker_thread_1?(p=0x0)?at?main.cpp:11
4311??????????????????????g?=?100;
44(gdb)?until?48
45worker_thread_2
46worker_thread_1
47
48Thread?2?"main"?hit?Breakpoint?2,?worker_thread_1?(p=0x0)?at?main.cpp:11
4911??????????????????????g?=?100;
50(gdb)
可以看到使用 step 模式鎖定的主線程,在使用 until 命令時另外兩個工作線程仍然有執(zhí)行的機會。我們再次切換到主線程,然后使用 next 命令單步調(diào)試下試試。
1(gdb)?info?threads
2??Id???Target?Id???????????????????????????????Frame?
3??1????Thread?0x7ffff7feb740?(LWP?1191)?"main"?0x00007ffff701bfad?in?nanosleep?()?from?/usr/lib64/libc.so.6
4*?2????Thread?0x7ffff6f56700?(LWP?1195)?"main"?worker_thread_1?(p=0x0)?at?main.cpp:11
5??3????Thread?0x7ffff6755700?(LWP?1196)?"main"?0x00007ffff701bfad?in?nanosleep?()?from?/usr/lib64/libc.so.6
6(gdb)?thread?1
7[Switching?to?thread?1?(Thread?0x7ffff7feb740?(LWP?1191))]
8#0??0x00007ffff701bfad?in?nanosleep?()?from?/usr/lib64/libc.so.6
9(gdb)?set?scheduler-locking?step
10(gdb)?next
11Single?stepping?until?exit?from?function?nanosleep,
12which?has?no?line?number?information.
130x00007ffff704c884?in?usleep?()?from?/usr/lib64/libc.so.6
14(gdb)?next
15Single?stepping?until?exit?from?function?usleep,
16which?has?no?line?number?information.
17main?()?at?main.cpp:40
1840??????????????????????g?=?-1;
19(gdb)?next
20
21Thread?1?"main"?hit?Breakpoint?1,?main?()?at?main.cpp:41
2241??????????????????????printf("g=%d\n",?g);
23(gdb)?next
24g=-1
2542??????????????????????g?=?-2;
26(gdb)?next
2743??????????????????????printf("g=%d\n",?g);
28(gdb)?next
29g=-2
3044??????????????????????g?=?-3;
31(gdb)?next
3245??????????????????????printf("g=%d\n",?g);
33(gdb)?next
34g=-3
3546??????????????????????g?=?-4;
36(gdb)?next
3747??????????????????????printf("g=%d\n",?g);
38(gdb)?next
39g=-4
4049??????????????????????usleep(1000000);
41(gdb)?next
4240??????????????????????g?=?-1;
43(gdb)?next
44
45Thread?1?"main"?hit?Breakpoint?1,?main?()?at?main.cpp:41
4641??????????????????????printf("g=%d\n",?g);
47(gdb)?next
48g=-1
4942??????????????????????g?=?-2;
50(gdb)?next
5143??????????????????????printf("g=%d\n",?g);
52(gdb)?next
53g=-2
5444??????????????????????g?=?-3;
55(gdb)?next
5645??????????????????????printf("g=%d\n",?g);
57(gdb)?next
58g=-3
5946??????????????????????g?=?-4;
60(gdb)?next
6147??????????????????????printf("g=%d\n",?g);
62(gdb)?next
63g=-4
6449??????????????????????usleep(1000000);
65(gdb)?next
6640??????????????????????g?=?-1;
67(gdb)?next
68
69Thread?1?"main"?hit?Breakpoint?1,?main?()?at?main.cpp:41
7041??????????????????????printf("g=%d\n",?g);
71(gdb)
此時我們發(fā)現(xiàn)設(shè)置了以 step 模式鎖定主線程,工作線程不會在單步調(diào)試主線程時被執(zhí)行,即使在工作線程設(shè)置了斷點。
最后我們使用 set scheduler-locking off 取消對主線程的鎖定,然后繼續(xù)使用 next 命令單步調(diào)試。
1(gdb)?set?scheduler-locking?off??
2(gdb)?next
3worker_thread_2
4worker_thread_1
5g=-100
642??????????????????????g?=?-2;
7(gdb)?next
8worker_thread_2
9[Switching?to?Thread?0x7ffff6f56700?(LWP?1195)]
10
11Thread?2?"main"?hit?Breakpoint?2,?worker_thread_1?(p=0x0)?at?main.cpp:11
1211??????????????????????g?=?100;
13(gdb)?next
14g=100
15g=-3
16g=-4
17worker_thread_2
1812??????????????????????printf("worker_thread_1\n");
19(gdb)?next
20worker_thread_1
2113??????????????????????usleep(300000);
22(gdb)?next
23worker_thread_2
24[Switching?to?Thread?0x7ffff7feb740?(LWP?1191)]
25
26Thread?1?"main"?hit?Breakpoint?1,?main?()?at?main.cpp:41
2741??????????????????????printf("g=%d\n",?g);
28(gdb)?next
29[Switching?to?Thread?0x7ffff6f56700?(LWP?1195)]
30
31Thread?2?"main"?hit?Breakpoint?2,?worker_thread_1?(p=0x0)?at?main.cpp:11
3211??????????????????????g?=?100;
33(gdb)?next
34g=-1
35g=-2
36g=-3
37g=-4
38worker_thread_2
3912??????????????????????printf("worker_thread_1\n");
40(gdb)?
取消了鎖定之后,單步調(diào)試時三個線程都有機會被執(zhí)行,線程 1 的斷點也會被正常觸發(fā)。
至此,我們搞清楚了如何利用 set scheduler-locking 選項來方便我們調(diào)試多線程程序。
總而言之,熟練掌握 gdb 調(diào)試等于擁有了學(xué)習(xí)優(yōu)秀 C/C++ 開源項目源碼的鑰匙,只要可以利用 gdb 調(diào)試,再復(fù)雜的項目,在不斷調(diào)試和分析過程中總會有搞明白的一天。
—?【 THE END 】—本公眾號全部博文已整理成一個目錄,請在公眾號里回復(fù)「m」獲??!
3T技術(shù)資源大放送!包括但不限于:Java、C/C++,Linux,Python,大數(shù)據(jù),人工智能等等。在公眾號內(nèi)回復(fù)「1024」,即可免費獲?。。?/span>
