成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一個(gè)耗時(shí)4小時(shí)的內(nèi)存泄漏問題

開發(fā) 后端
首先確定內(nèi)存泄漏問題出現(xiàn)的時(shí)間,發(fā)現(xiàn)在該時(shí)間點(diǎn)的上線有兩次代碼提交,其中一個(gè)就是我的。于是立刻排查這兩次代碼的改動(dòng),確定了另一個(gè)同事的代碼不可能會(huì)有內(nèi)存問題后我知道肯定是自己的代碼出現(xiàn)了問題。

上周像往常一樣例行檢查線上機(jī)器性能,突然發(fā)現(xiàn)一個(gè)服務(wù)的內(nèi)存使用率是這樣的:

很顯然該服務(wù)存在內(nèi)存泄漏問題,趕緊排查問題。

問題排查

首先確定內(nèi)存泄漏問題出現(xiàn)的時(shí)間,發(fā)現(xiàn)在該時(shí)間點(diǎn)的上線有兩次代碼提交,其中一個(gè)就是我的。于是立刻排查這兩次代碼的改動(dòng),確定了另一個(gè)同事的代碼不可能會(huì)有內(nèi)存問題后(因?yàn)榱硪粋€(gè)同事的上線僅僅修改了配置)我知道肯定是自己的代碼出現(xiàn)了問題。

確定了問題所在后趕緊把自己的代碼回滾掉,接下來就可以放心debug了。

Debug

什么是內(nèi)存泄漏?

簡(jiǎn)單的講就是程序員申請(qǐng)的內(nèi)存在使用完后沒有還給操作系統(tǒng),由于筆者使用的是C++語(yǔ)言,因此內(nèi)存泄漏一般是這樣的: 

  1. obj* o = new obj();  
  2. ...  
  3. // 使用完obj后沒有delete掉 

肯定有什么地方申請(qǐng)了內(nèi)存后沒有調(diào)用delete釋放內(nèi)存。

在這里介紹一下筆者的代碼改動(dòng),我的任務(wù)其實(shí)是重構(gòu)一段代碼,把這段代碼并行化。也就是舊的邏輯是在一個(gè)線程中串行執(zhí)行的,現(xiàn)在我要把這段邏輯放到兩個(gè)線程中并行執(zhí)行,這是最讓人頭疼的任務(wù)之一,并行化改造是比較容易出bug的。

接下來梳理了一遍中所有內(nèi)存的申請(qǐng)和釋放,這其中包括:

  •  使用new/delete分配釋放的內(nèi)存
  •  使用內(nèi)存池分配釋放的內(nèi)存

仔細(xì)梳理一遍后沒有發(fā)現(xiàn)任何問題,該釋放的內(nèi)存都已經(jīng)釋放掉了,這時(shí)筆者已經(jīng)開始懷疑人生了 :) ,很顯然還有一段沒有注意到的地方出現(xiàn)了問題,這是必然的,雖然知道問題必然出現(xiàn)在改動(dòng)的這些代碼里但是我并不能確定出現(xiàn)的位置。

沒有辦法,到這里基本上已經(jīng)要放棄自己人肉debug了,想利用一些內(nèi)存檢測(cè)工具來幫助自己確定問題。

常見的內(nèi)存泄漏檢測(cè)工具包括valgrind、gperftools等,valgrind的好處在于無需重新編譯代碼即可進(jìn)行內(nèi)存檢測(cè),但是缺點(diǎn)是會(huì)使得程序運(yùn)行非常緩慢,官方文檔給的說法是會(huì)比正常的程序運(yùn)行慢20-30倍;gperftools則需要重新編譯可執(zhí)行程序。這些工具需要下載安裝測(cè)試,其中還涉及到申請(qǐng)機(jī)器權(quán)限等問題,筆者覺得還是比較麻煩,況且這個(gè)問題也不是大海撈針一樣,問題肯定出在了并行化的這段代碼中。

到這里我決定再換一個(gè)思路來排查問題,既然代碼重構(gòu)后開始并行執(zhí)行,那么出現(xiàn)問題大概率是因?yàn)槎嗑€程問題,遇到多線程問題首先重點(diǎn)排查的就是線程間的共享數(shù)據(jù)。

多線程問題的關(guān)鍵——共享數(shù)據(jù)

我們知道如果線程之間沒有共享數(shù)據(jù)那么就不會(huì)有線程安全問題,我們使用的鎖、信號(hào)量、條件變量等其實(shí)都是用來保護(hù)共享數(shù)據(jù)的,比如鎖通常是用來包括臨界區(qū)的,臨界區(qū)中的代碼操作的就是線程共享數(shù)據(jù);信號(hào)量使用的一個(gè)經(jīng)典場(chǎng)景就是生產(chǎn)者消費(fèi)者問題,生產(chǎn)者線程以及消費(fèi)者線程都會(huì)操作同一個(gè)隊(duì)列,這里的隊(duì)列就是共享數(shù)據(jù)。

沿著這個(gè)思路開始找在兩個(gè)線程中都使用到的共享數(shù)據(jù),果不其然,在一個(gè)角落中發(fā)現(xiàn)了這樣一段代碼: 

  1. auto* pb = global->mutable_obj(); 

這是分配protobuf對(duì)象的一段代碼,protobuf是Google開發(fā)是一種類似于JSON、XML的技術(shù),因此常用于網(wǎng)絡(luò)通信和數(shù)據(jù)交換等場(chǎng)景,比如RPC等。

如果你不了解protobuf也沒有關(guān)系,實(shí)際上上面的這段代碼的要做的事情是這樣的: 

  1. if (global->obj == NULL) {  
  2.   global->obj = new obj();  
  3.  
  4. return global->obj; 

值得注意的是這段代碼現(xiàn)在會(huì)在兩個(gè)線程中執(zhí)行,顯然問題就出現(xiàn)在了這里。

那么問題是怎么出現(xiàn)的呢?

我們假設(shè)有兩個(gè)線程,線程A和線程B,當(dāng)這樣一段代碼在線程AB中同時(shí)執(zhí)行時(shí)可能會(huì)有以下場(chǎng)景:

  •  線程A拿到global->obj并檢測(cè)到此時(shí)的global->obj為空,因此決定為其分配內(nèi)存,但不巧的是此時(shí)發(fā)生線程切換,線程A在為global->obj分配內(nèi)存前被暫停運(yùn)行,如下所示: 
  1. if (global->obj == NULL) {  
  2.     <------- 線程切換,線程A被暫停執(zhí)行   
  3.     global->obj = new obj();  
  4.  
  5. return global->obj; 
  •  線程A被暫停運(yùn)行后線程B開始執(zhí)行,這段代碼同樣會(huì)在線程B中執(zhí)行一遍,因此線程B會(huì)首先檢查global->obj發(fā)現(xiàn)為空,因此為global->obj分配內(nèi)存,分配完內(nèi)存后發(fā)生線程切換,線程B被暫停運(yùn)行,如下所示: 
  1. if (global->obj == NULL) {  
  2.     global->obj = new obj();  
  3.     <------- 線程切換,線程B被暫停執(zhí)行   
  4.  
  5. return global->obj; 
  • 線程B被暫停運(yùn)行后調(diào)度器決定重新運(yùn)行線程A,此時(shí)線程A開始從被中斷的地方繼續(xù)運(yùn)行,還記得線程A是從哪里被中斷的嗎,沒錯(cuò),就是在為global->obj分配內(nèi)存前被中斷的,此時(shí)線程A繼續(xù)運(yùn)行,也就是說global->obj = new obj()這段代碼又被執(zhí)行了一次,雖然線程B已經(jīng)為global->obj分配了內(nèi)存。

Oops,典型的內(nèi)存泄漏,線程B分配的內(nèi)存再也無法被正常釋放掉了。

至此,我們已經(jīng)找到了問題的原因,罪魁禍?zhǔn)拙褪枪蚕頂?shù)據(jù),關(guān)鍵的一點(diǎn)是要意識(shí)到你的線程會(huì)隨時(shí)被中斷執(zhí)行,CPU會(huì)隨時(shí)切換到其它線程。

代碼修復(fù)也非常簡(jiǎn)單,再新增一個(gè)變量,兩個(gè)線程不在使用共享數(shù)據(jù),到這里問題就解決了,從發(fā)現(xiàn)問題到完成修復(fù)耗時(shí)大概4小時(shí)。

經(jīng)驗(yàn)教訓(xùn)

代碼的并行化重構(gòu)是一件非常棘手的任務(wù),很容易出現(xiàn)線程安全問題,解決線程安全問題首先要考慮的不是要不要加鎖,而是多個(gè)線程是否真的有必要使用共享數(shù)據(jù),沒有必要的話多個(gè)線程操作私有數(shù)據(jù)根本就不會(huì)出現(xiàn)線程安全問題。

當(dāng)出現(xiàn)線程安全問題時(shí),第一時(shí)間重點(diǎn)排查線程使用的共享數(shù)據(jù)。

內(nèi)存泄漏檢測(cè)工具

雖然這些沒有使用檢測(cè)工具全靠人肉debug其實(shí)還是因?yàn)閱栴}排查范圍比較小,如果我們根本就不知道問題出現(xiàn)在了那次代碼改動(dòng)那么檢測(cè)工具就非常重要了,在這里簡(jiǎn)單介紹一下valgrind的使用,詳細(xì)的介紹請(qǐng)參考官方文檔。

假設(shè)有這樣一段問題代碼: 

  1. #include <stdlib.h>  
  2. void f(void)    
  3.  
  4.    int* x = malloc(10 * sizeof(int));  
  5.    x[10] = 0;        // 問題1: 越界  
  6. }                    // 問題2: 內(nèi)存泄漏,x沒有被釋放掉   
  7. int main()   
  8.  
  9.    f();  
  10.    return 0;  

這段代碼中有兩個(gè)問題:一個(gè)是數(shù)據(jù)的越界訪問;另一個(gè)是內(nèi)存泄漏。將該程序編譯為myprog。

接下來使用valgrind來檢查該程序,使用以下命令: 

  1. valgrind --leak-check=yes myprog 

運(yùn)行完成后valgrind會(huì)給出檢測(cè)報(bào)告,關(guān)于程序越界訪問會(huì)給出這樣的輸出: 

  1. ==19182== Invalid write of size 4  
  2. ==19182==    at 0x804838F: f (example.c:6)  
  3. ==19182==    by 0x80483AB: main (example.c:11)  
  4. ==19182==  Address 0x1BA45050 is 0 bytes after a block of size 40 alloc'd  
  5. ==19182==    at 0x1B8FF5CD: malloc (vg_replace_malloc.c:130) 
  6. ==19182==    by 0x8048385: f (example.c:5)  
  7. ==19182==    by 0x80483AB: main (example.c:11) 

第一行告訴你代碼中存在Invalid write,也就是無效的寫,并給出了問題出現(xiàn)的位置。

關(guān)于內(nèi)存泄漏問題會(huì)給出這樣的輸出: 

  1. ==19182== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1  
  2. ==19182==    at 0x1B8FF5CD: malloc (vg_replace_malloc.c:130)  
  3. ==19182==    by 0x8048385: f (example.c:5)  
  4. ==19182==    by 0x80483AB: main (example.c:11) 

這里第一行報(bào)告了內(nèi)存"definitely lost",也就是說一定會(huì)存在內(nèi)存泄漏,并給出了問題出現(xiàn)的位置。

實(shí)際上除了"definitely lost",valgrind還會(huì)給出"probably lost"的報(bào)告,這兩種報(bào)告的含義是這樣的:

  •  "definitely lost":你的程序一定存在內(nèi)存泄漏問題,修復(fù)。
  •  "probably lost":你的程序看起來像是有內(nèi)存泄漏,有可能你在使用指針完成一些特定操作,因此不一定100%存在問題。

總結(jié)

編寫正確的多線程代碼從來不是一件容易的事情,線程安全問題的根源在于共享資源,因此在使用共享資源前務(wù)必確認(rèn)我們一定要用共享資源嗎? 

 

責(zé)任編輯:龐桂玉 來源: C語(yǔ)言與C++編程
相關(guān)推薦

2022-09-28 10:35:31

JavaScript代碼內(nèi)存泄漏

2020-12-15 10:52:44

CIO企業(yè)網(wǎng)

2024-02-21 08:00:55

WindowsDWM進(jìn)程

2025-05-06 15:31:17

陶哲軒AI工具

2022-07-08 09:43:24

攜程酒店數(shù)據(jù)接口服務(wù)平臺(tái)

2010-04-02 10:29:02

CentOS安裝

2024-01-30 10:12:00

Java內(nèi)存泄漏

2012-08-03 09:51:55

程序員編程

2012-07-04 14:40:37

Ajax

2018-10-25 15:24:10

ThreadLocal內(nèi)存泄漏Java

2017-01-05 19:34:06

漏洞nodejs代碼

2024-03-22 13:31:00

線程策略線程池

2019-11-20 15:02:45

Java虛擬機(jī)內(nèi)存

2016-11-24 15:54:06

androidJSONObject

2012-06-05 00:26:58

程序員

2019-05-09 14:42:41

安吉智能倉(cāng)庫(kù)

2010-09-26 15:38:33

JVM內(nèi)存泄漏

2023-03-17 07:44:24

IntelDDR4內(nèi)存

2022-05-31 06:07:45

Excel表Python

2024-12-23 11:41:45

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 久久精品在线免费视频 | 国产精品久久国产精品久久 | 国产色婷婷 | 成人h动漫亚洲一区二区 | 国产一区 在线视频 | 中文字幕在线精品 | 日韩在线精品 | 久久国产欧美日韩精品 | 波多野吉衣久久 | 99re6在线视频 | 国产激情一区二区三区 | 免费同性女女aaa免费网站 | 在线一区 | 国产一区二区三区四区 | 欧美日韩成人 | 一级毛片大全免费播放 | 亚洲综合日韩精品欧美综合区 | 欧美成人a∨高清免费观看 老司机午夜性大片 | 91久久精品国产91久久 | 麻豆精品一区二区三区在线观看 | 国产日韩欧美一区 | 精品一二区 | 在线日韩| 成人h片在线观看 | 亚洲精品久久久 | 国产一区二区三区免费视频 | 一级黄色片日本 | 91精品国产乱码久久久久久久久 | 日韩精品在线一区 | 成人免费一区二区三区牛牛 | 久久久久久中文字幕 | 精品国产一区一区二区三亚瑟 | 亚洲欧美网 | 天天干天天爱天天 | 一区二区三区网站 | 亚州成人 | 紧缚调教一区二区三区视频 | 亚洲精品自拍 | 欧美日韩高清免费 | 欧美一区二区三区小说 | 高清视频一区二区三区 |