go語(yǔ)言面試之Goroutine詳解
在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 泄漏的步驟:
- 安裝goleak
go get -u golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow go get -u github.com/uber-go/goleak
- 我們編寫一段會(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)題。
- 下面我們右鍵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è)方面:
Goroutine的創(chuàng)建與使用
面試官可能會(huì)問(wèn)你如何在Go中創(chuàng)建Goroutine,如何同步多個(gè)Goroutine的執(zhí)行,如何確保主函數(shù)在所有Goroutine完成后再退出。通道(Channel)的使用
作為Go中非常重要的并發(fā)工具,通道的使用是面試中的常見問(wèn)題。你可能會(huì)被要求用通道來(lái)解決一些常見的并發(fā)問(wèn)題,比如Goroutine之間的數(shù)據(jù)傳遞、同步等。Goroutine的生命周期
面試官可能會(huì)問(wèn)你如何管理Goroutine的生命周期,尤其是如何避免Goroutine泄漏(Goroutine Leak),即某個(gè)Goroutine無(wú)法退出或被及時(shí)回收。調(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性能的建議:
減少Goroutine的數(shù)量
不要隨意創(chuàng)建大量的Goroutine。創(chuàng)建過(guò)多的Goroutine會(huì)導(dǎo)致調(diào)度器的開銷增加,從而影響程序的整體性能。使用緩沖通道
如果你需要在Goroutine之間傳遞大量數(shù)據(jù),可以使用緩沖通道來(lái)減少阻塞和等待時(shí)間。避免共享內(nèi)存
Go語(yǔ)言提倡通過(guò)通道而不是共享內(nèi)存來(lái)進(jìn)行Goroutine之間的通信。避免共享內(nèi)存可以避免競(jìng)態(tài)條件,并使代碼更加可預(yù)測(cè)。合理使用
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)文章希望大家以后多多支持腳本之家!
- Go語(yǔ)言輕量級(jí)線程Goroutine用法實(shí)例
- go獲取協(xié)程(goroutine)號(hào)的實(shí)例
- go中控制goroutine數(shù)量的方法
- Golang 探索對(duì)Goroutine的控制方法(詳解)
- Go 防止 goroutine 泄露的方法
- 淺析Golang中的協(xié)程(goroutine)
- golang goroutine順序輸出方式
- Go語(yǔ)言死鎖與goroutine泄露問(wèn)題的解決
- Go語(yǔ)言CSP并發(fā)模型goroutine及channel底層實(shí)現(xiàn)原理
- Go語(yǔ)言之使用pprof工具查找goroutine(協(xié)程)泄漏
相關(guān)文章
詳解如何使用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ù)操作使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04
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)體的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04
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語(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é)作與搶占詳解
如果某個(gè)G執(zhí)行時(shí)間過(guò)長(zhǎng),其他的G如何才能被正常調(diào)度,這就引出了接下來(lái)的話題:協(xié)作與搶占。本文將通過(guò)一些示例為大家詳細(xì)講講調(diào)度器中協(xié)作與搶占的相關(guān)知識(shí),需要的可以參考一下2023-04-04

