第二章 Goroutine泄漏的調(diào)試

在我們談?wù)搮f(xié)程(Goroutines)泄漏之前燃少,我們先看看并發(fā)編程的概念把鉴。并發(fā)編程處理程序的并發(fā)執(zhí)行故响。多個連續(xù)流任務(wù)通過并發(fā)編程同時(shí)執(zhí)行检盼,得到更快的執(zhí)行完成肯污。對于運(yùn)行在多核處理器上的現(xiàn)代軟件,并發(fā)編程是必要的吨枉,它有助于更好地利用多核處理器的功能蹦渣,實(shí)現(xiàn)更快的并發(fā)/并行程序。

協(xié)程 (Goroutines)

協(xié)程實(shí)現(xiàn)了并發(fā)執(zhí)行貌亭,協(xié)程是Go運(yùn)行時(shí)輕量級線程柬唯,協(xié)程和線程之間并無一對一的關(guān)系,協(xié)程由Go管理調(diào)度属提,運(yùn)行在不同的線程上权逗。Go協(xié)程的設(shè)計(jì)隱藏了許多線程創(chuàng)建和管理方面的復(fù)雜工作。

關(guān)于并發(fā)/并行程序冤议,并發(fā)程序可能是并行的斟薇,也可能不是。并行是一種通過使用多處理器以提高速度的能力恕酸。一個設(shè)計(jì)良好的并發(fā)程序在并行方面的表現(xiàn)也非常出色堪滨。在Go語言中,為了使你的程序可以使用多核運(yùn)行蕊温,這時(shí)協(xié)程就真正的是并行運(yùn)行了袱箱,你必須使用GOMAXPROCS變量。詳細(xì)參考:https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/14.1.md

同步 (synchronize)

進(jìn)程义矛、線程发笔、協(xié)程協(xié)作都有一個共同的目標(biāo):同步和通訊。

Go語言中凉翻,Channels用于協(xié)程的同步了讨。傳統(tǒng)線程模式通訊是共享內(nèi)存。Go鼓勵使用Channel在協(xié)程之間傳遞引用,而不是顯式地使用鎖來協(xié)調(diào)對共享數(shù)據(jù)的訪問前计。 這種方法確保在給定時(shí)間只有一個goroutine可以訪問數(shù)據(jù)胞谭。

如下面的例子所示,每個worker執(zhí)行完成后男杈,他們需要與main協(xié)程協(xié)作丈屹,將返回結(jié)果通過channels傳遞給main協(xié)程,之后main協(xié)程退出程序伶棒。

同步出錯

請注意旺垒,每次使用go關(guān)鍵字時(shí),Go例程將如何退出苞冯。有時(shí)候同步可能出現(xiàn)錯誤袖牙,導(dǎo)致一些goroutine永遠(yuǎn)等待侧巨。在Go語言中舅锄,如下情況可能導(dǎo)致同步出錯:

Channel沒有接受者

沒有一個接受者來接受發(fā)送者發(fā)送的數(shù)據(jù),Channel是阻塞的司忱。沒有接受者的Channel會引起程序掛起皇忿。下面的例子,ch1沒有接受者坦仍,將導(dǎo)致Channel是阻塞的鳍烁。

package main

import "fmt"

func?main() {

ch1 :=make(chanint)

go pump(ch1)// pump hangs

fmt.Println(<-ch1)// prints only 0

}

funcpump(chchanint) {

fori :=0; ; i++ {

ch <- i

}

}

Channel沒有寫入者

如下情況會出現(xiàn)channel沒有寫入者的情況,會出現(xiàn)goroutine泄漏繁扎。

例 1: for-select

for {

select {

case <-c:

// process here

}

}

例 2: channel循環(huán)

go func() {

for range ch { }

}()

例3: 演示tasks循環(huán)幔荒,導(dǎo)致channel沒有寫入者,需要主程序調(diào)用close(tasks)來避免goroutine泄漏問題梳玫。

package main

import "fmt"

func concurrency() {

// lets first create a channel with a buffer

tasks := make(chan string, 20)

// create another one to receive the results

results := make(chan string, 20)

workers := []int{1, 2, 3, 4}

// inserting tasks inside the channel

for task := 0; task < 10; task++ {

tasks <- fmt.Sprintf("Task %d", task)

}

for _, w := range workers {

// starging one goroutine for each worker

go work(w, tasks, results)

}

close(tasks)

// lets print the resutls

fmt.Println("Will print the results")

for res := 0; res < 10; res++ {

fmt.Println("Result:", <-results)

}

}

func work(workerID int, tasks chan string, results chan string) {

// worker will block util a new task arrives in the channel

for t := range tasks {

// simple task as example

results <- fmt.Sprintf("Worker %d got %v", workerID, t)

}

}

func main() {

concurrency()

}

好的做法

使用timeOut

timeout := make(chan bool, 1)

go func() {

time.Sleep(1e9) // one second

timeout <- true

}()

select {

case <- ch:

// a read from ch has occurred

case <- timeout:

// the read from ch has timed out

}?????????? OR select {

case res := <-c1:

fmt.Println(res)

case <-time.After(time.Second * 1):

fmt.Println("timeout 1")

}

使用Golang context package

Golang context package可以用來優(yōu)雅地結(jié)束例程甚至超時(shí)

泄漏檢測

儀器(instrumentation)端點(diǎn)

檢測Web服務(wù)器泄漏的辦法是添加儀器端點(diǎn)爹梁,并將其與負(fù)載測試一起使用。

// get the count of number of go routines in the system.

func countGoRoutines() int {

returnruntime.NumGoroutine()

}

func getGoroutinesCountHandler(w http.ResponseWriter, r *http.Request) {

// Get the count of number of go routines running.

count := countGoRoutines()

w.Write([]byte(strconv.Itoa(count)))

}

func main() {

http.HandleFunc("/_count", getGoroutinesCountHandler)

}

在負(fù)載測試之前和之后提澎,通過儀器端點(diǎn)響應(yīng)在系統(tǒng)中存在的goroutines數(shù)量姚垃。以下是負(fù)載測試程序的流程:

Step 1: Call the instrumentation endpoint and get the count of number of goroutines alive in your webserver.

Step 2: Perform load test.Lets the load be concurrent.

for i := 0; i < 100 ; i++ {

go callEndpointUnderInvestigation()

}

Step 3: Call the instrumentation endpoint and get the count of number of goroutines alive in your webserver.

如果負(fù)載測試后系統(tǒng)中存在異常增加的goroutine數(shù)量,則證明存在泄漏盼忌。這是一個具有漏洞端點(diǎn)的Web服務(wù)器的小例子积糯。 通過簡單的測試我們可以確定服務(wù)器是否存在泄漏。

// First run the leaky server $ go run leaky-server.go

// Run the load test now.$ go run load.go

3 Go routines before the load test in the system.

54 Go routines after the load test in the system.

您可以清楚地看到谦纱,通過50個并發(fā)請求到泄漏端點(diǎn)看成,系統(tǒng)中增加了50個程序。

讓我們再次運(yùn)行負(fù)載測試跨嘉。

$ go run load.go

53 Go routines before the load test in the system.

104 Go routines after the load test in the system.

很清楚川慌,在每次運(yùn)行的負(fù)載測試中,服務(wù)器中的執(zhí)行次數(shù)都在增加,而不是下降窘游。 這是一個明顯的泄漏證據(jù)唠椭。

識別泄漏的起因

使用棧跟蹤端點(diǎn)

一旦發(fā)現(xiàn)Web服務(wù)器中存在泄漏,需要確定泄漏的來源忍饰√吧可以通過添加返回Web服務(wù)器的棧跟蹤端點(diǎn)可以幫助識別泄漏的來源。

import (

"runtime/debug"

"runtime/pprof"

)

func getStackTraceHandler(w http.ResponseWriter, r *http.Request) {

stack := debug.Stack()

w.Write(stack)

pprof.Lookup("goroutine").WriteTo(w, 2)

}

func main() {

http.HandleFunc("/_stack", getStackTraceHandler)

}

在確定泄漏的存在之后艾蓝,使用端點(diǎn)在負(fù)載之前和之后獲取棧跟蹤信息力崇,以識別泄漏的來源。

將棧跟蹤工具添加到泄漏服務(wù)器并再次執(zhí)行負(fù)載測試赢织。

如下棧跟蹤信息清楚地指出泄漏的震中:

// First run the leaky server$ go run leaky-server.go

// Run the load test now.$ go run load.go

3 Go routines before the load test in the system.

54 Go routines after the load test in the system. goroutine 149 [chan send]:

main.sum(0xc420122e58, 0x3, 0x3, 0xc420112240)

/home/karthic/gophercon/count-instrument.go:39 +0x6c

created by main.sumConcurrent

/home/karthic/gophercon/count-instrument.go:51 +0x12b

goroutine 243 [chan send]:

main.sum(0xc42021a0d8, 0x3, 0x3, 0xc4202760c0)

/home/karthic/gophercon/count-instrument.go:39 +0x6c

created by main.sumConcurrent

/home/karthic/gophercon/count-instrument.go:51 +0x12b

goroutine 259 [chan send]:

main.sum(0xc4202700d8, 0x3, 0x3, 0xc42029c0c0)

/home/karthic/gophercon/count-instrument.go:39 +0x6c

created by main.sumConcurrent

/home/karthic/gophercon/count-instrument.go:51 +0x12b

goroutine 135 [chan send]:

main.sum(0xc420226348, 0x3, 0x3, 0xc4202363c0)

/home/karthic/gophercon/count-instrument.go:39 +0x6c

created by main.sumConcurrent

/home/karthic/gophercon/count-instrument.go:51 +0x12b

goroutine 166 [chan send]:

main.sum(0xc4202482b8, 0x3, 0x3, 0xc42006b8c0)

/home/karthic/gophercon/count-instrument.go:39 +0x6c

created by main.sumConcurrent

/home/karthic/gophercon/count-instrument.go:51 +0x12b

goroutine 199 [chan send]:

main.sum(0xc420260378, 0x3, 0x3, 0xc420256480)

/home/karthic/gophercon/count-instrument.go:39 +0x6c

created by main.sumConcurrent

/home/karthic/gophercon/count-instrument.go:51 +0x12b

........

使用profiling

由于泄漏的goroutine通常被阻止去嘗試讀取或?qū)懭隿hannel或甚至可能睡眠亮靴,profilling分析將幫助識別泄漏的起因。參見benchmarks and profiling談?wù)摶鶞?zhǔn)測試和分析于置,或https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/13.10.md茧吊。

避免泄漏,趕早不趕晚

單元測試和功能測試中使用instrument機(jī)制可以幫助早期識別泄漏八毯。計(jì)數(shù)試驗(yàn)前后的goroutine數(shù)搓侄。

func TestMyFunc() {

// get count of go routines. perform the test.

// get the count diff.

// alert if there's an unexpected rise.

}

測試中的棧差異

棧差異是一個簡單的程序,它在測試之前和之后對棧跟蹤進(jìn)行差異比較话速,并在任何不期望的goroutine遺留的系統(tǒng)情況下發(fā)出警報(bào)讶踪。 將將其與單元測試和功能測試集成,可以幫助在開發(fā)過程中識別泄漏泊交。

import (

github.com/fortytw2/leaktest

)

func TestMyFunc(t *testing.T) {

defer leaktest.Check(t)()

go func() {

for {

time.Sleep(time.Second)

}

}()

}

安全設(shè)計(jì)

當(dāng)系統(tǒng)受到一個端點(diǎn)/服務(wù)受到泄漏或資源中斷影響的時(shí)候乳讥,微服務(wù)架構(gòu)的服務(wù)做為獨(dú)立容器/過程運(yùn)行可以保護(hù)整個系統(tǒng)。推薦使用容器編排工具廓俭,如Kubernetes云石,Mesosphere和Docker Swarm。

Goroutine泄漏就像慢性自殺白指。設(shè)想獲取整個系統(tǒng)的棧跟蹤留晚,并嘗試識別哪些服務(wù)導(dǎo)致數(shù)百個服務(wù)中的泄漏! 真的嚇人!!!! 他們在一段時(shí)間浪費(fèi)你的計(jì)算資源告嘲,慢慢積累错维,你甚至不會注意到。 真的很重要去意識到泄漏并盡早調(diào)試它們橄唬!

Go will make you love programming again. I promise.

Go會讓你再次愛編程赋焕。 我承諾。

參考:

1.《The Way to Go》中文譯本《Go入門指南》https://github.com/Unknwon/the-way-to-go_ZH_CN

2. Debugging go routine leaks:https://youtu.be/hWo0FEVr92A

3.?https://github.com/fortytw2/leaktest

4.?http://www.tuicool.com/articles/2AZf63J

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仰楚,一起剝皮案震驚了整個濱河市隆判,隨后出現(xiàn)的幾起案子犬庇,更是在濱河造成了極大的恐慌,老刑警劉巖侨嘀,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件臭挽,死亡現(xiàn)場離奇詭異,居然都是意外死亡咬腕,警方通過查閱死者的電腦和手機(jī)欢峰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涨共,“玉大人纽帖,你說我怎么就攤上這事【俜矗” “怎么了懊直?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長火鼻。 經(jīng)常有香客問我室囊,道長,這世上最難降的妖魔是什么凝危? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任波俄,我火速辦了婚禮晨逝,結(jié)果婚禮上蛾默,老公的妹妹穿的比我還像新娘。我一直安慰自己捉貌,他們只是感情好支鸡,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著趁窃,像睡著了一般牧挣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上醒陆,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天瀑构,我揣著相機(jī)與錄音,去河邊找鬼刨摩。 笑死寺晌,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的澡刹。 我是一名探鬼主播呻征,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼罢浇!你這毒婦竟也來了陆赋?” 一聲冷哼從身側(cè)響起沐祷,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎攒岛,沒想到半個月后赖临,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡灾锯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年思杯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挠进。...
    茶點(diǎn)故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡色乾,死狀恐怖领突,靈堂內(nèi)的尸體忽然破棺而出暖璧,到底是詐尸還是另有隱情,我是刑警寧澤君旦,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布澎办,位于F島的核電站,受9級特大地震影響金砍,放射性物質(zhì)發(fā)生泄漏局蚀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一恕稠、第九天 我趴在偏房一處隱蔽的房頂上張望琅绅。 院中可真熱鬧,春花似錦鹅巍、人聲如沸千扶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽澎羞。三九已至,卻和暖如春敛苇,著一層夾襖步出監(jiān)牢的瞬間妆绞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工枫攀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留括饶,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓脓豪,卻偏偏與公主長得像巷帝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子扫夜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 01.{ 換行: Opening Brace Can't Be Placed on a Separate Lin...
    碼農(nóng)不器閱讀 2,397評論 0 14
  • 當(dāng)潮流愛新鮮 當(dāng)旁人愛標(biāo)簽 幸得伴著你我 是窩心的自然
    寒夜龍吟閱讀 133評論 0 0
  • 2017-7-26 --著:江涵小子 丁環(huán)小寶 今生少年緣夢吟 得遇碧佳思美人 且知兒于曾夢時(shí) 還有丁環(huán)...
    江涵少年閱讀 179評論 0 2
  • 燈光閃爍 我心失落 一切還是沒結(jié)果 有酒有歌 無你只我 我心仍舊很執(zhí)著 當(dāng)初你總要如何如何 如今看來全都是錯 愛是...
    三碗再過崗閱讀 197評論 2 2