欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

go語(yǔ)言面試之Goroutine詳解

 更新時(shí)間:2025年09月04日 10:29:15   作者:360-go-php  
本文介紹了Go語(yǔ)言中Goroutine泄漏問(wèn)題及其解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

在Go語(yǔ)言的面試中,Goroutine 是一個(gè)非常重要的話題,因?yàn)樗荊o語(yǔ)言并發(fā)編程的核心。Goroutine為我們提供了輕量級(jí)的線程,并且能夠讓程序在多核處理器上更高效地運(yùn)行。在這篇文章中,我將詳細(xì)討論Goroutine的基本概念、如何使用它以及一些面試中常見的考察點(diǎn)。

一、Goroutine的基本概念

在Go語(yǔ)言中,Goroutine 是一種并發(fā)執(zhí)行的輕量級(jí)線程。每個(gè)Goroutine都有獨(dú)立的??臻g,默認(rèn)情況下,Go的運(yùn)行時(shí)系統(tǒng)會(huì)為每個(gè)Goroutine分配2KB的棧空間。??臻g是動(dòng)態(tài)擴(kuò)展的,這使得Go能夠同時(shí)運(yùn)行成千上萬(wàn)的Goroutine,而不會(huì)像傳統(tǒng)線程那樣占用大量的內(nèi)存資源。

1. 什么是Goroutine泄漏

在生活中泄漏通常指的是某個(gè)容易的故障導(dǎo)致資源的浪費(fèi),比如汽車的油箱破了導(dǎo)致汽油泄漏,而在計(jì)算機(jī)領(lǐng)域,泄漏(Leakage)通常指的是某些進(jìn)程/資源因?yàn)楸贿z忘而忘記回收,占用著系統(tǒng)的資源,最終導(dǎo)致系統(tǒng)資源枯竭而宕機(jī)。

在日常GO語(yǔ)言的編碼中創(chuàng)建協(xié)程是很容易的,但是對(duì)goroutine生命周期的管理是不容易的。具體來(lái)說(shuō)如果你創(chuàng)建了一個(gè)協(xié)程(goroutine),你以為這個(gè)goroutine最終會(huì)按照預(yù)期完成自己的任務(wù)然后結(jié)束執(zhí)行,但是由于某些原因,這個(gè)goroutine一直沒(méi)有終止,他占用著系統(tǒng)的資源永不釋放,無(wú)人管控,最終可能因?yàn)榇罅看嬖谶@一類泄漏的協(xié)程,系統(tǒng)資源消耗殆盡,導(dǎo)致主程序意外終止或者系統(tǒng)崩潰。

2. 監(jiān)控泄漏的goroutine

日常開發(fā)中遇到goroutine泄漏通常是比較難

2.1 goleak

github地址 https://github.com/uber-go/goleak

goleak 是一個(gè) Go 語(yǔ)言的工具,用于檢查 goroutine 泄漏的情況。它可以在測(cè)試代碼中使用,自動(dòng)檢查測(cè)試結(jié)束時(shí)是否有 goroutine 泄漏,并將泄漏的 goroutine 信息輸出。

下面是使用 goleak 檢查 goroutine 泄漏的步驟:

  1. 安裝goleak
go get -u golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
go get -u github.com/uber-go/goleak
  1. 我們編寫一段會(huì)產(chǎn)生goroutine泄漏的代碼
func LeakDemo1(){
	ch := make(chan int) // 創(chuàng)建一個(gè)無(wú)緩沖區(qū)的通道
 
	go func() {
		val := <-ch
		fmt.Println("哈哈哈哈",val)
	}()
}

然后我們?cè)赿emo1目錄下創(chuàng)建一個(gè)main_test文件,里面編寫一下內(nèi)容(有g(shù)oLand這個(gè)IDE的可以直接在LeakDemo函數(shù)上右鍵 -> generate -> Test for fuction可以幫助我們快速創(chuàng)建Testing文件)

package main
 
import (
	"go.uber.org/goleak"
	"testing"
)
func TestLeakDemo1(t *testing.T) {
	defer goleak.VerifyNone(t)
	LeakDemo1()
}

這樣,當(dāng)測(cè)試用例結(jié)束時(shí),goleak 會(huì)檢查當(dāng)前是否有 goroutine 泄漏,如果有,則會(huì)在控制臺(tái)輸出相關(guān)信息,幫助開發(fā)人員快速地定位和解決問(wèn)題。

  1. 下面我們右鍵main_test文件運(yùn)行

發(fā)現(xiàn)答案了,goleak幫我們尋找出來(lái)了對(duì)應(yīng)的泄漏代碼,如下圖出現(xiàn)了 found unexpected goroutines 的字樣,說(shuō)明出現(xiàn)了協(xié)程泄漏。

VerifyTestMain的運(yùn)行結(jié)果與VerifyNone有一點(diǎn)不同,VerifyTestMain會(huì)先報(bào)告測(cè)試用例執(zhí)行結(jié)果,然后報(bào)告泄漏分析,如果測(cè)試的用例中有多個(gè)goroutine泄漏,無(wú)法精確定位到發(fā)生泄漏的具體test,需要使用如下腳本進(jìn)一步分析:

# Create a test binary which will be used to run each test individually
$ go test -c -o tests
 
# Run each test individually, printing "." for successful tests, or the test name
# for failing tests.
$ for test in $(go test -list . | grep -E "^(Test|Example)"); do ./tests -test.run "^$test\$" &>/dev/null && echo -n "." || echo -e "\n$test failed"; done

需要注意的是,goleak 只能檢查 goroutine 泄漏的情況,不能檢查內(nèi)存泄漏或者其他類型的資源泄漏。如果需要檢查內(nèi)存泄漏等問(wèn)題,需要使用其他工具進(jìn)行分析。

2.2 goleak實(shí)現(xiàn)原理

從VerifyNone入口,我們查看源代碼,其調(diào)用了Find方法:

// Find looks for extra goroutines, and returns a descriptive error if
// any are found.
func Find(options ...Option) error {
  // 獲取當(dāng)前goroutine的ID
 cur := stack.Current().ID()
 
 opts := buildOpts(options...)
 var stacks []stack.Stack
 retry := true
 for i := 0; retry; i++ {
    // 過(guò)濾無(wú)用的goroutine
  stacks = filterStacks(stack.All(), cur, opts)
 
  if len(stacks) == 0 {
   return nil
  }
  retry = opts.retry(i)
 }
 
 return fmt.Errorf("found unexpected goroutines:\n%s", stacks)
}

我們?cè)诳匆幌耭ilterStacks方法:

// filterStacks will filter any stacks excluded by the given opts.
// filterStacks modifies the passed in stacks slice.
func filterStacks(stacks []stack.Stack, skipID int, opts *opts) []stack.Stack {
 filtered := stacks[:0]
 for _, stack := range stacks {
  // Always skip the running goroutine.
  if stack.ID() == skipID {
   continue
  }
  // Run any default or user-specified filters.
  if opts.filter(stack) {
   continue
  }
  filtered = append(filtered, stack)
 }
 return filtered
}

這里主要是過(guò)濾掉一些不參與檢測(cè)的goroutine stack,如果沒(méi)有自定義filters,則使用默認(rèn)的filters:

func buildOpts(options ...Option) *opts {
 opts := &opts{
  maxRetries: _defaultRetries,
  maxSleep:   100 * time.Millisecond,
 }
 opts.filters = append(opts.filters,
  isTestStack,
  isSyscallStack,
  isStdLibStack,
  isTraceStack,
 )
 for _, option := range options {
  option.apply(opts)
 }
 return opts
}

從這里可以看出,默認(rèn)檢測(cè)20次,每次默認(rèn)間隔100ms;添加默認(rèn)filters;

總結(jié)一下goleak的實(shí)現(xiàn)原理:

使用runtime.Stack()方法獲取當(dāng)前運(yùn)行的所有g(shù)oroutine的棧信息,默認(rèn)定義不需要檢測(cè)的過(guò)濾項(xiàng),默認(rèn)定義檢測(cè)次數(shù)+檢測(cè)間隔,不斷周期進(jìn)行檢測(cè),最終在多次檢查后仍沒(méi)有找到剩下的goroutine則判斷沒(méi)有發(fā)生goroutine泄漏。

3. 常見的Goroutine泄漏的原因

  • Goroutine 內(nèi)正在進(jìn)行 channel/mutex 等讀寫操作,但由于邏輯問(wèn)題,某些情況下會(huì)被一直阻塞。
  • Goroutine 內(nèi)的業(yè)務(wù)邏輯進(jìn)入死循環(huán),資源一直無(wú)法釋放。
  • Goroutine 內(nèi)的業(yè)務(wù)邏輯進(jìn)入長(zhǎng)時(shí)間等待,有不斷新增的 Goroutine 進(jìn)入等待。

3.1 無(wú)緩沖區(qū)的channel

對(duì)于無(wú)緩沖區(qū)的channel來(lái)說(shuō),最經(jīng)典的goroutine泄漏無(wú)非就是協(xié)程在監(jiān)聽無(wú)緩沖的channel,由于對(duì)于無(wú)緩沖區(qū)的channle來(lái)說(shuō),無(wú)論是從channel中讀還是往channel中寫數(shù)據(jù),都會(huì)造成阻塞。所以很容易出現(xiàn)泄漏問(wèn)題

A 從空緩存channel中recv數(shù)據(jù)

func leak() {
    ch := make(chan int)
 
    go func() {
        val := <-ch // 這行代碼將永遠(yuǎn)被阻塞
        fmt.Println("We received a value:", val) // 這行代碼永遠(yuǎn)不會(huì)執(zhí)行
    }()
}

上面代碼中,函數(shù)leak函數(shù)創(chuàng)建了一個(gè)無(wú)緩沖區(qū)channel,并且啟動(dòng)一個(gè)goroutine從ch管道中接收元素,由于ch管道是無(wú)緩沖管道并且沒(méi)有任何協(xié)程往ch中發(fā)送數(shù)據(jù),所以第5行代碼 val := <- ch 將會(huì)永久阻塞,永遠(yuǎn)不會(huì)執(zhí)行l(wèi)eak函數(shù)的第6行代碼。

B 往空緩存channel里Send數(shù)據(jù)

同理,如果往一個(gè)無(wú)緩沖的channel中發(fā)送數(shù)據(jù)也會(huì)造成goroutine阻塞

func leak() {
    ch := make(chan int)
 
    go func() {
        val := <-ch // 這行代碼將永遠(yuǎn)被阻塞
        fmt.Println("We received a value:", val) // 這行代碼永遠(yuǎn)不會(huì)執(zhí)行
    }()
}

但是沒(méi)有管理好他的生命周期,導(dǎo)致這些協(xié)程被遺忘,讓本該結(jié)束退出的協(xié)程因?yàn)闆](méi)有管制而永遠(yuǎn)在后臺(tái)隱秘的運(yùn)行,并占用著系統(tǒng)的資源,直到系統(tǒng)資源枯竭,程序退出。

1.1 被遺忘的發(fā)送者

// search
// @Description: 定義一個(gè)搜索接口
// @param term :  搜索的關(guān)鍵字
// @return string : 搜索的返回結(jié)果
// @return error
func search(keyword string)(string,error){
	time.Sleep(time.Millisecond * 200) // 模擬業(yè)務(wù)執(zhí)行搜索需要的時(shí)間 假設(shè)是200毫秒
	return "搜索結(jié)果",nil
}
 
type result struct{
	record string
	err error
}
 
func process(key string) error{
	ctx,cancel := context.WithTimeout(context.Background(),100*time.Millisecond) // 設(shè)置如果超過(guò)100毫秒沒(méi)有返回就終止協(xié)程
	defer cancel()
 
	ch := make(chan result)
 
	// 開啟協(xié)程,執(zhí)行后臺(tái)搜索
	go func() {
		record,err := search(key)
		ch <- result{
			record: record,
			err: err,
		}
	}()
	select{
	case <- ctx.Done():
		return errors.New("搜索超時(shí),被動(dòng)搜索") // 因?yàn)樵O(shè)置了100毫秒的限制
		case res :=<-ch:
			if res.err != nil{
				return res.err
			}
			fmt.Println("返回搜索結(jié)果",res.record)
			return nil
	}
}

整個(gè)函數(shù)的執(zhí)行流程是這樣的

由于超時(shí)時(shí)間不合理,加上無(wú)緩沖區(qū)管道,導(dǎo)致最終協(xié)程泄漏

使用有緩沖區(qū)通道

如果我們將無(wú)緩沖區(qū)通道修改為有緩沖區(qū)通道,那么

ctx,cancel := context.WithTimeout(context.Background(),100*time.Millisecond) // 設(shè)置如果超過(guò)100毫秒沒(méi)有返回就終止協(xié)程
defer cancel()
 
ch := make(chan result,1)

現(xiàn)在,在超時(shí)情況下,在接收器移動(dòng)之后,搜索Goroutine將通過(guò)將結(jié)果值放置在通道中完成發(fā)送,然后返回。該Goroutine的內(nèi)存以及通道的內(nèi)存最終將被回收。一切都會(huì)自然而然地解決。

1.2 半途而廢的協(xié)程

在go語(yǔ)言中,有個(gè)規(guī)則:

程序先執(zhí)行init函數(shù),然后執(zhí)行main函數(shù),在main函數(shù)執(zhí)行完成并返回時(shí)候,程序退出,不會(huì)等待其他任何子goroutine,所謂的子goroutine就是我們使用go關(guān)鍵字啟動(dòng)的協(xié)程

規(guī)范很清楚,當(dāng)程序從主函數(shù)返回時(shí),您的程序不會(huì)等待任何未完成的Goroutine執(zhí)行完畢。這是一件好事!想想讓一個(gè)Goroutine泄漏或讓一個(gè)Goroutine運(yùn)行很長(zhǎng)時(shí)間是多么容易。如果您的程序在終止之前等待非主Goroutine完成,它可能會(huì)陷入某種僵尸狀態(tài),永遠(yuǎn)不會(huì)終止。

然而,當(dāng)您啟動(dòng)一個(gè)Goroutine來(lái)做一些重要的事情,但主函數(shù)不知道等待它完成時(shí),這種終止行為就會(huì)成為一個(gè)問(wèn)題。這種類型的場(chǎng)景可能會(huì)導(dǎo)致完整性問(wèn)題,例如損壞數(shù)據(jù)庫(kù)、文件系統(tǒng)或丟失數(shù)據(jù)。

type Tracker struct {
 
}
 
func (t *Tracker) Event(data string){
	time.Sleep(time.Millisecond)
	log.Println(data)
}
 
type App struct{
	tracker Tracker
}
 
func(a *App)Handler(w http.ResponseWriter,r *http.Request){
	// 實(shí)際編寫業(yè)務(wù)代碼
	
	// 返回給客戶端
	
	w.WriteHeader(http.StatusOK)
	
	go a.tracker.Event("記錄埋點(diǎn)")
}

代碼的重要部分是第21行。這就是在新Goroutine的范圍內(nèi)調(diào)用a.track.Event方法的地方。這具有異步跟蹤事件而不增加請(qǐng)求延遲的預(yù)期效果。然而,這段代碼落入了不完整的工作陷阱,必須進(jìn)行重構(gòu)。在第21行創(chuàng)建的任何Goroutine都不能保證運(yùn)行或完成。這是一個(gè)完整性問(wèn)題,因?yàn)榉?wù)器關(guān)閉時(shí)可能會(huì)丟失事件。

擔(dān)保的重構(gòu)

為了避免陷阱,團(tuán)隊(duì)修改了Tracker類型來(lái)管理Goroutines本身。該類型使用sync.WaitGroup來(lái)保持打開的Goroutine的計(jì)數(shù),并為要調(diào)用的主函數(shù)提供Shutdown方法,該方法將等待所有Goroutine完成。

首先,處理程序被修改為不直接創(chuàng)建Goroutine。清單4中唯一的變化是它不再包含go關(guān)鍵字去異步調(diào)用。

func(a *App)Handler(w http.ResponseWriter,r *http.Request){
	// 實(shí)際編寫業(yè)務(wù)代碼
	
	// 返回給客戶端
	
	w.WriteHeader(http.StatusOK)
	
	a.tracker.Event("記錄埋點(diǎn)")
}

接下來(lái),Tracker類型被重寫以管理Goroutines本身。

type Tracker struct {
	wg sync.WaitGroup
}
 
// Event starts tracking an event. It runs asynchronously to
// not block the caller. Be sure to call the Shutdown function
// before the program exits so all tracked events finish.
func (t *Tracker) Event(data string) {
	t.wg.Add(1)
	go func() {
		defer t.wg.Done()
		time.Sleep(time.Millisecond) // Simulate network write latency.
		log.Println(data)
	}()
}
func(t *Tracker) Shutdown() {
	t.wg.Wait()
}

在清單5中,第12行將sync.WaitGroup添加到Tracker的類型定義中。在第21行的Event方法中,調(diào)用了t.wg.Add(1)。這會(huì)使計(jì)數(shù)器遞增,以說(shuō)明在第24行創(chuàng)建的Goroutine。一旦創(chuàng)建了Goroutine,Event函數(shù)就會(huì)返回滿足客戶端最小化事件跟蹤延遲要求的結(jié)果。創(chuàng)建的Goroutine執(zhí)行其工作,完成后在第27行調(diào)用t.wg.done()。調(diào)用Done方法會(huì)減少計(jì)數(shù)器,以便WaitGroup知道此Goroutine已完成。

對(duì)Add和Done的調(diào)用對(duì)于跟蹤活動(dòng)Goroutine的數(shù)量很有用,但程序仍必須被指示等待它們完成。為此,Tracker類型在第35行獲得了一個(gè)新的方法Shutdown。此函數(shù)的最簡(jiǎn)單實(shí)現(xiàn)是調(diào)用t.wg.Wit(),該函數(shù)將一直阻塞,直到Goroutine計(jì)數(shù)減為0。最后,必須從func main調(diào)用此方法,如清單6所示:

func main() {
	
	// Start a server.
	// Details not shown...
	var a App
	
	// Shut the server down.
	// Details not shown...
	
	// Wait for all event goroutines to finish.
	a.track.Shutdown()
}

清單6的重要部分是第66行,它阻止func main終止,直到a.track.Shutdown()完成。

但也許不要等太久

Shutdown方法的實(shí)現(xiàn)很簡(jiǎn)單,可以完成所需的工作;它等待Goroutines完成。不幸的是,它等待的時(shí)間沒(méi)有限制。根據(jù)您的生產(chǎn)環(huán)境,您可能不愿意無(wú)限期地等待程序關(guān)閉。為了給Shutdown方法添加截止日期,團(tuán)隊(duì)將其更改為:

清單7

// Shutdown waits for all tracked events to finish processing
// or for the provided context to be canceled.
func (t *Tracker) Shutdown(ctx context.Context) error {
 
    // Create a channel to signal when the waitgroup is finished.
    ch := make(chan struct{})
 
    // Create a goroutine to wait for all other goroutines to
    // be done then close the channel to unblock the select.
    go func() {
        t.wg.Wait()
        close(ch)
    }()
 
    // Block this function from returning. Wait for either the
    // waitgroup to finish or the context to expire.
    select {
    case <-ch:
        return nil
    case <-ctx.Done():
        return errors.New("timeout")
    }
}

現(xiàn)在在清單7的第38行,Shutdown方法接受context.context作為輸入。這就是調(diào)用者如何限制允許關(guān)機(jī)等待的時(shí)間。在第41行的函數(shù)中,創(chuàng)建一個(gè)頻道,然后在第45行啟動(dòng)一個(gè)Goroutine。這個(gè)新的Goroutine的唯一任務(wù)是等待WaitGroup完成,然后關(guān)閉頻道。最后,行52開始一個(gè)選擇塊,它等待上下文被取消或頻道被關(guān)閉。

接下來(lái),團(tuán)隊(duì)將func main中的調(diào)用更改為:

清單8

// Wait up to 5 seconds for all event goroutines to finish.
const timeout = 5 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
 
err := a.track.Shutdown(ctx)

在清單8中,在主函數(shù)中創(chuàng)建了一個(gè)具有5秒超時(shí)的上下文。這將傳遞給.track.Shutdown以設(shè)置main愿意等待多長(zhǎng)時(shí)間的限制。

結(jié)論

隨著Goroutines的引入,該服務(wù)器的處理程序能夠?qū)⑿枰櫴录腁PI客戶端的延遲成本降至最低。只使用go關(guān)鍵字在后臺(tái)運(yùn)行這項(xiàng)工作很容易,但該解決方案存在完整性問(wèn)題。要做到這一點(diǎn),需要在關(guān)閉程序之前努力確保所有相關(guān)的Goroutine都已終止。

并發(fā)是一個(gè)有用的工具,但必須謹(jǐn)慎使用。

有人說(shuō)可以使用pprof來(lái)排查,雖然其可以達(dá)到目的,但是這些性能分析工具往往是在出現(xiàn)問(wèn)題后借助其輔助排查使用的,有沒(méi)有一款可以防患于未然的工具嗎?當(dāng)然有,goleak他來(lái)了,其由 Uber 團(tuán)隊(duì)開源,可以用來(lái)檢測(cè)goroutine泄漏,并且可以結(jié)合單元測(cè)試,可以達(dá)到防范于未然的目的,本文我們就一起來(lái)看一看goleak。

不知道你們?cè)谌粘i_發(fā)中是否有遇到過(guò)goroutine泄漏,goroutine泄漏其實(shí)就是goroutine阻塞,這些阻塞的goroutine會(huì)一直存活直到進(jìn)程終結(jié),他們占用的棧內(nèi)存一直無(wú)法釋放,從而導(dǎo)致系統(tǒng)的可用內(nèi)存會(huì)越來(lái)越少,直至崩潰!簡(jiǎn)單總結(jié)了幾種常見的泄漏原因:

  • Goroutine內(nèi)的邏輯進(jìn)入死循壞,一直占用資源
  • Goroutine配合channel/mutex使用時(shí),由于使用不當(dāng)導(dǎo)致一直被阻塞
  • Goroutine內(nèi)的邏輯長(zhǎng)時(shí)間等待,導(dǎo)致Goroutine數(shù)量暴增

接下來(lái)我們使用Goroutine+channel的經(jīng)典組合來(lái)展示goroutine泄漏;

func GetData() {
 var ch chan struct{}
 go func() {
  <- ch
 }()
}
 
func main()  {
 defer func() {
  fmt.Println("goroutines: ", runtime.NumGoroutine())
 }()
 GetData()
 time.Sleep(2 * time.Second)
}

這個(gè)例子是channel忘記初始化,無(wú)論是讀寫操作都會(huì)造成阻塞,這個(gè)方法如果是寫單測(cè)是檢查不出來(lái)問(wèn)題的:

func TestGetData(t *testing.T) {
 GetData()
}
=== RUN   TestGetData
--- PASS: TestGetData (0.00s)
PASS

內(nèi)置測(cè)試無(wú)法滿足,接下來(lái)我們引入goleak來(lái)測(cè)試一下。

goleak

github地址https://github.com/uber-go/goleak

使用goleak主要關(guān)注兩個(gè)方法即可:VerifyNone、VerifyTestMain,VerifyNone用于單一測(cè)試用例中測(cè)試,VerifyTestMain可以在TestMain中添加,可以減少對(duì)測(cè)試代碼的入侵,舉例如下:

使用VerifyNone:

func TestGetDataWithGoleak(t *testing.T) {
 defer goleak.VerifyNone(t)
 GetData()
}

運(yùn)行結(jié)果

=== RUN   TestGetDataWithGoleak
    leaks.go:78: found unexpected goroutines:
        [Goroutine 35 in state chan receive (nil chan), with asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector.GetData.func1 on top of the stack:
        goroutine 35 [chan receive (nil chan)]:
        asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector.GetData.func1()
         /Users/go/src/asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector/main.go:12 +0x1f
        created by asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector.GetData
         /Users/go/src/asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector/main.go:11 +0x3c
        ]
--- FAIL: TestGetDataWithGoleak (0.45s)
 
FAIL
 
Process finished with the exit code 1

通過(guò)運(yùn)行結(jié)果看到具體發(fā)生goroutine泄漏的具體代碼段;使用VerifyNone會(huì)對(duì)我們的測(cè)試代碼有入侵,可以采用VerifyTestMain方法可以更快的集成到測(cè)試中:

func TestMain(m *testing.M) {
 goleak.VerifyTestMain(m)
}

=== RUN   TestGetData
--- PASS: TestGetData (0.00s)
PASS
goleak: Errors on successful test run: found unexpected goroutines:
[Goroutine 5 in state chan receive (nil chan), with asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector.GetData.func1 on top of the stack:
goroutine 5 [chan receive (nil chan)]:
asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector.GetData.func1()
 /Users/go/src/asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector/main.go:12 +0x1f
created by asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector.GetData
 /Users/go/src/asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector/main.go:11 +0x3c
]
 
Process finished with the exit code 1

二、面試常見考察點(diǎn)

在Go語(yǔ)言面試中,關(guān)于Goroutine的考察通常會(huì)包括以下幾個(gè)方面:

  1. Goroutine的創(chuàng)建與使用
    面試官可能會(huì)問(wèn)你如何在Go中創(chuàng)建Goroutine,如何同步多個(gè)Goroutine的執(zhí)行,如何確保主函數(shù)在所有Goroutine完成后再退出。

  2. 通道(Channel)的使用
    作為Go中非常重要的并發(fā)工具,通道的使用是面試中的常見問(wèn)題。你可能會(huì)被要求用通道來(lái)解決一些常見的并發(fā)問(wèn)題,比如Goroutine之間的數(shù)據(jù)傳遞、同步等。

  3. Goroutine的生命周期
    面試官可能會(huì)問(wèn)你如何管理Goroutine的生命周期,尤其是如何避免Goroutine泄漏(Goroutine Leak),即某個(gè)Goroutine無(wú)法退出或被及時(shí)回收。

  4. 調(diào)度和性能
    你可能會(huì)被問(wèn)到Go的調(diào)度機(jī)制,如何高效地利用多個(gè)CPU核心,如何通過(guò)調(diào)整 GOMAXPROCS 來(lái)影響調(diào)度器的行為,以及如何診斷和優(yōu)化并發(fā)程序的性能。

三、Goroutine的性能優(yōu)化

盡管Goroutine非常輕量,但它的性能仍然受到調(diào)度器的影響。如果Goroutine的數(shù)量過(guò)多,或者它們頻繁進(jìn)行上下文切換,可能會(huì)導(dǎo)致性能下降。以下是一些優(yōu)化Goroutine性能的建議:

  1. 減少Goroutine的數(shù)量
    不要隨意創(chuàng)建大量的Goroutine。創(chuàng)建過(guò)多的Goroutine會(huì)導(dǎo)致調(diào)度器的開銷增加,從而影響程序的整體性能。

  2. 使用緩沖通道
    如果你需要在Goroutine之間傳遞大量數(shù)據(jù),可以使用緩沖通道來(lái)減少阻塞和等待時(shí)間。

  3. 避免共享內(nèi)存
    Go語(yǔ)言提倡通過(guò)通道而不是共享內(nèi)存來(lái)進(jìn)行Goroutine之間的通信。避免共享內(nèi)存可以避免競(jìng)態(tài)條件,并使代碼更加可預(yù)測(cè)。

  4. 合理使用 sync.Pool
    在需要頻繁創(chuàng)建和銷毀對(duì)象的場(chǎng)景下,可以考慮使用 sync.Pool 來(lái)重用對(duì)象,避免頻繁的內(nèi)存分配和垃圾回收開銷。

四、總結(jié)

Goroutine是Go語(yǔ)言的核心特性之一,它為并發(fā)編程提供了非常高效且簡(jiǎn)潔的解決方案。在面試中,Goroutine相關(guān)的問(wèn)題不僅考察你的并發(fā)編程能力,還考察你對(duì)Go語(yǔ)言內(nèi)部機(jī)制的理解。通過(guò)掌握Goroutine的創(chuàng)建、同步、調(diào)度和性能優(yōu)化技巧,能夠在Go語(yǔ)言面試中表現(xiàn)得更加出色。

希望通過(guò)這篇文章,對(duì)Goroutine有了更加深入的理解,并能夠在實(shí)際工作中靈活應(yīng)用這些知識(shí)。如果正在準(zhǔn)備Go語(yǔ)言面試,建議多做一些實(shí)際的并發(fā)編程練習(xí),掌握如何有效地使用Goroutine來(lái)處理高并發(fā)問(wèn)題。

到此這篇關(guān)于go語(yǔ)言面試之Goroutine詳解的文章就介紹到這了,更多相關(guān)go Goroutine內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 分析Go語(yǔ)言接口的設(shè)計(jì)原則

    分析Go語(yǔ)言接口的設(shè)計(jì)原則

    interface是Go語(yǔ)言的基礎(chǔ)特性之一, 可以理解為對(duì)一種類型的規(guī)范或者約束。他跟java、c++不同, Go語(yǔ)言實(shí)現(xiàn)接口不需要顯示說(shuō)明實(shí)現(xiàn)了哪個(gè)接口, 也沒(méi)有繼承或者子類或者implement關(guān)鍵字。只是通過(guò)約定的形式, 隱式的實(shí)現(xiàn)接口中的方法即可
    2021-06-06
  • go-zero服務(wù)部署配置及源碼解讀

    go-zero服務(wù)部署配置及源碼解讀

    這篇文章主要為大家介紹了go-zero服務(wù)部署配置及源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • 詳解如何使用go-acme/lego實(shí)現(xiàn)自動(dòng)簽發(fā)證書

    詳解如何使用go-acme/lego實(shí)現(xiàn)自動(dòng)簽發(fā)證書

    這篇文章主要為大家詳細(xì)介紹了如何使用?go-acme/lego?的客戶端或庫(kù)完成證書的自動(dòng)簽發(fā),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-03-03
  • golang連接mysql數(shù)據(jù)庫(kù)操作使用示例

    golang連接mysql數(shù)據(jù)庫(kù)操作使用示例

    這篇文章主要為大家介紹了golang連接mysql數(shù)據(jù)庫(kù)操作使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • Go語(yǔ)言開發(fā)快速學(xué)習(xí)CGO編程

    Go語(yǔ)言開發(fā)快速學(xué)習(xí)CGO編程

    這篇文章主要為大家介紹了Go語(yǔ)言開發(fā)之快速學(xué)習(xí)CGO編程,看了本文你就會(huì)發(fā)現(xiàn)CGO編程其實(shí)沒(méi)有想象的那么難,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • golang 如何用反射reflect操作結(jié)構(gòu)體

    golang 如何用反射reflect操作結(jié)構(gòu)體

    這篇文章主要介紹了golang 用反射reflect操作結(jié)構(gòu)體的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-04-04
  • go語(yǔ)言for循環(huán)中嵌套defer的執(zhí)行順序

    go語(yǔ)言for循環(huán)中嵌套defer的執(zhí)行順序

    在Go語(yǔ)言中,defer語(yǔ)句用于延遲函數(shù)調(diào)用的執(zhí)行,本文主要介紹了go語(yǔ)言for循環(huán)中嵌套defer的執(zhí)行順序,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-03-03
  • GO實(shí)現(xiàn)跳躍表的示例詳解

    GO實(shí)現(xiàn)跳躍表的示例詳解

    跳表全稱叫做跳躍表,簡(jiǎn)稱跳表,是一個(gè)隨機(jī)化的數(shù)據(jù)結(jié)構(gòu),實(shí)質(zhì)就是一種可以進(jìn)行二分查找的有序鏈表。本文將利用GO語(yǔ)言編寫一個(gè)跳表,需要的可以參考一下
    2022-12-12
  • go語(yǔ)言如何使用gin庫(kù)實(shí)現(xiàn)SSE長(zhǎng)連接

    go語(yǔ)言如何使用gin庫(kù)實(shí)現(xiàn)SSE長(zhǎng)連接

    所謂長(zhǎng)連接指在一個(gè)TCP連接上可以連續(xù)發(fā)送多個(gè)數(shù)據(jù)包,在TCP連接保持期間,如果沒(méi)有數(shù)據(jù)包發(fā)送,需要雙方發(fā)檢測(cè)包以維持此連接,一般需要自己做在線維持,下面這篇文章主要給大家介紹了關(guān)于go語(yǔ)言如何使用gin庫(kù)實(shí)現(xiàn)SSE長(zhǎng)連接的相關(guān)資料,需要的朋友可以參考下
    2023-06-06
  • Go調(diào)度器學(xué)習(xí)之協(xié)作與搶占詳解

    Go調(diào)度器學(xué)習(xí)之協(xié)作與搶占詳解

    如果某個(gè)G執(zhí)行時(shí)間過(guò)長(zhǎng),其他的G如何才能被正常調(diào)度,這就引出了接下來(lái)的話題:協(xié)作與搶占。本文將通過(guò)一些示例為大家詳細(xì)講講調(diào)度器中協(xié)作與搶占的相關(guān)知識(shí),需要的可以參考一下
    2023-04-04

最新評(píng)論