go語言面試之Goroutine詳解
在Go語言的面試中,Goroutine
是一個非常重要的話題,因為它是Go語言并發(fā)編程的核心。Goroutine為我們提供了輕量級的線程,并且能夠讓程序在多核處理器上更高效地運行。在這篇文章中,我將詳細討論Goroutine的基本概念、如何使用它以及一些面試中常見的考察點。
一、Goroutine的基本概念
在Go語言中,Goroutine 是一種并發(fā)執(zhí)行的輕量級線程。每個Goroutine都有獨立的??臻g,默認情況下,Go的運行時系統(tǒng)會為每個Goroutine分配2KB的??臻g。??臻g是動態(tài)擴展的,這使得Go能夠同時運行成千上萬的Goroutine,而不會像傳統(tǒng)線程那樣占用大量的內(nèi)存資源。
1. 什么是Goroutine泄漏
在生活中泄漏通常指的是某個容易的故障導(dǎo)致資源的浪費,比如汽車的油箱破了導(dǎo)致汽油泄漏,而在計算機領(lǐng)域,泄漏(Leakage)通常指的是某些進程/資源因為被遺忘而忘記回收,占用著系統(tǒng)的資源,最終導(dǎo)致系統(tǒng)資源枯竭而宕機。
在日常GO語言的編碼中創(chuàng)建協(xié)程是很容易的,但是對goroutine生命周期的管理是不容易的。具體來說如果你創(chuàng)建了一個協(xié)程(goroutine),你以為這個goroutine最終會按照預(yù)期完成自己的任務(wù)然后結(jié)束執(zhí)行,但是由于某些原因,這個goroutine一直沒有終止,他占用著系統(tǒng)的資源永不釋放,無人管控,最終可能因為大量存在這一類泄漏的協(xié)程,系統(tǒng)資源消耗殆盡,導(dǎo)致主程序意外終止或者系統(tǒng)崩潰。
2. 監(jiān)控泄漏的goroutine
日常開發(fā)中遇到goroutine泄漏通常是比較難
2.1 goleak
github地址 https://github.com/uber-go/goleak
goleak 是一個 Go 語言的工具,用于檢查 goroutine 泄漏的情況。它可以在測試代碼中使用,自動檢查測試結(jié)束時是否有 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
- 我們編寫一段會產(chǎn)生goroutine泄漏的代碼
func LeakDemo1(){ ch := make(chan int) // 創(chuàng)建一個無緩沖區(qū)的通道 go func() { val := <-ch fmt.Println("哈哈哈哈",val) }() }
然后我們在demo1目錄下創(chuàng)建一個main_test文件,里面編寫一下內(nèi)容(有g(shù)oLand這個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)測試用例結(jié)束時,goleak 會檢查當(dāng)前是否有 goroutine 泄漏,如果有,則會在控制臺輸出相關(guān)信息,幫助開發(fā)人員快速地定位和解決問題。
- 下面我們右鍵main_test文件運行
發(fā)現(xiàn)答案了,goleak幫我們尋找出來了對應(yīng)的泄漏代碼,如下圖出現(xiàn)了 found unexpected goroutines
的字樣,說明出現(xiàn)了協(xié)程泄漏。
VerifyTestMain的運行結(jié)果與VerifyNone有一點不同,VerifyTestMain會先報告測試用例執(zhí)行結(jié)果,然后報告泄漏分析,如果測試的用例中有多個goroutine泄漏,無法精確定位到發(fā)生泄漏的具體test,需要使用如下腳本進一步分析:
# 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)存泄漏等問題,需要使用其他工具進行分析。
2.2 goleak實現(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++ { // 過濾無用的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) }
我們在看一下filterStacks方法:
// 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 }
這里主要是過濾掉一些不參與檢測的goroutine stack,如果沒有自定義filters,則使用默認的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 }
從這里可以看出,默認檢測20次,每次默認間隔100ms;添加默認filters;
總結(jié)一下goleak的實現(xiàn)原理:
使用runtime.Stack()方法獲取當(dāng)前運行的所有g(shù)oroutine的棧信息,默認定義不需要檢測的過濾項,默認定義檢測次數(shù)+檢測間隔,不斷周期進行檢測,最終在多次檢查后仍沒有找到剩下的goroutine則判斷沒有發(fā)生goroutine泄漏。
3. 常見的Goroutine泄漏的原因
- Goroutine 內(nèi)正在進行 channel/mutex 等讀寫操作,但由于邏輯問題,某些情況下會被一直阻塞。
- Goroutine 內(nèi)的業(yè)務(wù)邏輯進入死循環(huán),資源一直無法釋放。
- Goroutine 內(nèi)的業(yè)務(wù)邏輯進入長時間等待,有不斷新增的 Goroutine 進入等待。
3.1 無緩沖區(qū)的channel
對于無緩沖區(qū)的channel來說,最經(jīng)典的goroutine泄漏無非就是協(xié)程在監(jiān)聽無緩沖的channel,由于對于無緩沖區(qū)的channle來說,無論是從channel中讀還是往channel中寫數(shù)據(jù),都會造成阻塞。所以很容易出現(xiàn)泄漏問題
A 從空緩存channel中recv數(shù)據(jù)
func leak() { ch := make(chan int) go func() { val := <-ch // 這行代碼將永遠被阻塞 fmt.Println("We received a value:", val) // 這行代碼永遠不會執(zhí)行 }() }
上面代碼中,函數(shù)leak函數(shù)創(chuàng)建了一個無緩沖區(qū)channel,并且啟動一個goroutine從ch管道中接收元素,由于ch管道是無緩沖管道并且沒有任何協(xié)程往ch中發(fā)送數(shù)據(jù),所以第5行代碼 val := <- ch
將會永久阻塞,永遠不會執(zhí)行l(wèi)eak函數(shù)的第6行代碼。
B 往空緩存channel里Send數(shù)據(jù)
同理,如果往一個無緩沖的channel中發(fā)送數(shù)據(jù)也會造成goroutine阻塞
func leak() { ch := make(chan int) go func() { val := <-ch // 這行代碼將永遠被阻塞 fmt.Println("We received a value:", val) // 這行代碼永遠不會執(zhí)行 }() }
但是沒有管理好他的生命周期,導(dǎo)致這些協(xié)程被遺忘,讓本該結(jié)束退出的協(xié)程因為沒有管制而永遠在后臺隱秘的運行,并占用著系統(tǒng)的資源,直到系統(tǒng)資源枯竭,程序退出。
1.1 被遺忘的發(fā)送者
// search // @Description: 定義一個搜索接口 // @param term : 搜索的關(guān)鍵字 // @return string : 搜索的返回結(jié)果 // @return error func search(keyword string)(string,error){ time.Sleep(time.Millisecond * 200) // 模擬業(yè)務(wù)執(zhí)行搜索需要的時間 假設(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è)置如果超過100毫秒沒有返回就終止協(xié)程 defer cancel() ch := make(chan result) // 開啟協(xié)程,執(zhí)行后臺搜索 go func() { record,err := search(key) ch <- result{ record: record, err: err, } }() select{ case <- ctx.Done(): return errors.New("搜索超時,被動搜索") // 因為設(shè)置了100毫秒的限制 case res :=<-ch: if res.err != nil{ return res.err } fmt.Println("返回搜索結(jié)果",res.record) return nil } }
整個函數(shù)的執(zhí)行流程是這樣的
由于超時時間不合理,加上無緩沖區(qū)管道,導(dǎo)致最終協(xié)程泄漏
使用有緩沖區(qū)通道
如果我們將無緩沖區(qū)通道修改為有緩沖區(qū)通道,那么
ctx,cancel := context.WithTimeout(context.Background(),100*time.Millisecond) // 設(shè)置如果超過100毫秒沒有返回就終止協(xié)程 defer cancel() ch := make(chan result,1)
現(xiàn)在,在超時情況下,在接收器移動之后,搜索Goroutine將通過將結(jié)果值放置在通道中完成發(fā)送,然后返回。該Goroutine的內(nèi)存以及通道的內(nèi)存最終將被回收。一切都會自然而然地解決。
1.2 半途而廢的協(xié)程
在go語言中,有個規(guī)則:
程序先執(zhí)行init函數(shù),然后執(zhí)行main函數(shù),在main函數(shù)執(zhí)行完成并返回時候,程序退出,不會等待其他任何子goroutine,所謂的子goroutine就是我們使用go關(guān)鍵字啟動的協(xié)程
規(guī)范很清楚,當(dāng)程序從主函數(shù)返回時,您的程序不會等待任何未完成的Goroutine執(zhí)行完畢。這是一件好事!想想讓一個Goroutine泄漏或讓一個Goroutine運行很長時間是多么容易。如果您的程序在終止之前等待非主Goroutine完成,它可能會陷入某種僵尸狀態(tài),永遠不會終止。
然而,當(dāng)您啟動一個Goroutine來做一些重要的事情,但主函數(shù)不知道等待它完成時,這種終止行為就會成為一個問題。這種類型的場景可能會導(dǎo)致完整性問題,例如損壞數(shù)據(jù)庫、文件系統(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){ // 實際編寫業(yè)務(wù)代碼 // 返回給客戶端 w.WriteHeader(http.StatusOK) go a.tracker.Event("記錄埋點") }
代碼的重要部分是第21行。這就是在新Goroutine的范圍內(nèi)調(diào)用a.track.Event方法的地方。這具有異步跟蹤事件而不增加請求延遲的預(yù)期效果。然而,這段代碼落入了不完整的工作陷阱,必須進行重構(gòu)。在第21行創(chuàng)建的任何Goroutine都不能保證運行或完成。這是一個完整性問題,因為服務(wù)器關(guān)閉時可能會丟失事件。
擔(dān)保的重構(gòu)
為了避免陷阱,團隊修改了Tracker類型來管理Goroutines本身。該類型使用sync.WaitGroup來保持打開的Goroutine的計數(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){ // 實際編寫業(yè)務(wù)代碼 // 返回給客戶端 w.WriteHeader(http.StatusOK) a.tracker.Event("記錄埋點") }
接下來,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)。這會使計數(shù)器遞增,以說明在第24行創(chuàng)建的Goroutine。一旦創(chuàng)建了Goroutine,Event函數(shù)就會返回滿足客戶端最小化事件跟蹤延遲要求的結(jié)果。創(chuàng)建的Goroutine執(zhí)行其工作,完成后在第27行調(diào)用t.wg.done()。調(diào)用Done方法會減少計數(shù)器,以便WaitGroup知道此Goroutine已完成。
對Add和Done的調(diào)用對于跟蹤活動Goroutine的數(shù)量很有用,但程序仍必須被指示等待它們完成。為此,Tracker類型在第35行獲得了一個新的方法Shutdown。此函數(shù)的最簡單實現(xiàn)是調(diào)用t.wg.Wit(),該函數(shù)將一直阻塞,直到Goroutine計數(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方法的實現(xiàn)很簡單,可以完成所需的工作;它等待Goroutines完成。不幸的是,它等待的時間沒有限制。根據(jù)您的生產(chǎn)環(huán)境,您可能不愿意無限期地等待程序關(guān)閉。為了給Shutdown方法添加截止日期,團隊將其更改為:
清單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)機等待的時間。在第41行的函數(shù)中,創(chuàng)建一個頻道,然后在第45行啟動一個Goroutine。這個新的Goroutine的唯一任務(wù)是等待WaitGroup完成,然后關(guān)閉頻道。最后,行52開始一個選擇塊,它等待上下文被取消或頻道被關(guān)閉。
接下來,團隊將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)建了一個具有5秒超時的上下文。這將傳遞給.track.Shutdown以設(shè)置main愿意等待多長時間的限制。
結(jié)論
隨著Goroutines的引入,該服務(wù)器的處理程序能夠?qū)⑿枰櫴录腁PI客戶端的延遲成本降至最低。只使用go關(guān)鍵字在后臺運行這項工作很容易,但該解決方案存在完整性問題。要做到這一點,需要在關(guān)閉程序之前努力確保所有相關(guān)的Goroutine都已終止。
并發(fā)是一個有用的工具,但必須謹慎使用。
有人說可以使用pprof來排查,雖然其可以達到目的,但是這些性能分析工具往往是在出現(xiàn)問題后借助其輔助排查使用的,有沒有一款可以防患于未然的工具嗎?當(dāng)然有,goleak他來了,其由 Uber 團隊開源,可以用來檢測goroutine泄漏,并且可以結(jié)合單元測試,可以達到防范于未然的目的,本文我們就一起來看一看goleak。
不知道你們在日常開發(fā)中是否有遇到過goroutine泄漏,goroutine泄漏其實就是goroutine阻塞,這些阻塞的goroutine會一直存活直到進程終結(jié),他們占用的棧內(nèi)存一直無法釋放,從而導(dǎo)致系統(tǒng)的可用內(nèi)存會越來越少,直至崩潰!簡單總結(jié)了幾種常見的泄漏原因:
- Goroutine內(nèi)的邏輯進入死循壞,一直占用資源
- Goroutine配合channel/mutex使用時,由于使用不當(dāng)導(dǎo)致一直被阻塞
- Goroutine內(nèi)的邏輯長時間等待,導(dǎo)致Goroutine數(shù)量暴增
接下來我們使用Goroutine+channel的經(jīng)典組合來展示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) }
這個例子是channel忘記初始化,無論是讀寫操作都會造成阻塞,這個方法如果是寫單測是檢查不出來問題的:
func TestGetData(t *testing.T) { GetData() }
=== RUN TestGetData --- PASS: TestGetData (0.00s) PASS
內(nèi)置測試無法滿足,接下來我們引入goleak來測試一下。
goleak
github地址:https://github.com/uber-go/goleak
使用goleak主要關(guān)注兩個方法即可:VerifyNone、VerifyTestMain,VerifyNone用于單一測試用例中測試,VerifyTestMain可以在TestMain中添加,可以減少對測試代碼的入侵,舉例如下:
使用VerifyNone:
func TestGetDataWithGoleak(t *testing.T) { defer goleak.VerifyNone(t) GetData() }
運行結(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
通過運行結(jié)果看到具體發(fā)生goroutine泄漏的具體代碼段;使用VerifyNone會對我們的測試代碼有入侵,可以采用VerifyTestMain方法可以更快的集成到測試中:
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
二、面試常見考察點
在Go語言面試中,關(guān)于Goroutine的考察通常會包括以下幾個方面:
Goroutine的創(chuàng)建與使用
面試官可能會問你如何在Go中創(chuàng)建Goroutine,如何同步多個Goroutine的執(zhí)行,如何確保主函數(shù)在所有Goroutine完成后再退出。通道(Channel)的使用
作為Go中非常重要的并發(fā)工具,通道的使用是面試中的常見問題。你可能會被要求用通道來解決一些常見的并發(fā)問題,比如Goroutine之間的數(shù)據(jù)傳遞、同步等。Goroutine的生命周期
面試官可能會問你如何管理Goroutine的生命周期,尤其是如何避免Goroutine泄漏(Goroutine Leak),即某個Goroutine無法退出或被及時回收。調(diào)度和性能
你可能會被問到Go的調(diào)度機制,如何高效地利用多個CPU核心,如何通過調(diào)整GOMAXPROCS
來影響調(diào)度器的行為,以及如何診斷和優(yōu)化并發(fā)程序的性能。
三、Goroutine的性能優(yōu)化
盡管Goroutine非常輕量,但它的性能仍然受到調(diào)度器的影響。如果Goroutine的數(shù)量過多,或者它們頻繁進行上下文切換,可能會導(dǎo)致性能下降。以下是一些優(yōu)化Goroutine性能的建議:
減少Goroutine的數(shù)量
不要隨意創(chuàng)建大量的Goroutine。創(chuàng)建過多的Goroutine會導(dǎo)致調(diào)度器的開銷增加,從而影響程序的整體性能。使用緩沖通道
如果你需要在Goroutine之間傳遞大量數(shù)據(jù),可以使用緩沖通道來減少阻塞和等待時間。避免共享內(nèi)存
Go語言提倡通過通道而不是共享內(nèi)存來進行Goroutine之間的通信。避免共享內(nèi)存可以避免競態(tài)條件,并使代碼更加可預(yù)測。合理使用
sync.Pool
在需要頻繁創(chuàng)建和銷毀對象的場景下,可以考慮使用sync.Pool
來重用對象,避免頻繁的內(nèi)存分配和垃圾回收開銷。
四、總結(jié)
Goroutine是Go語言的核心特性之一,它為并發(fā)編程提供了非常高效且簡潔的解決方案。在面試中,Goroutine相關(guān)的問題不僅考察你的并發(fā)編程能力,還考察你對Go語言內(nèi)部機制的理解。通過掌握Goroutine的創(chuàng)建、同步、調(diào)度和性能優(yōu)化技巧,能夠在Go語言面試中表現(xiàn)得更加出色。
希望通過這篇文章,對Goroutine有了更加深入的理解,并能夠在實際工作中靈活應(yīng)用這些知識。如果正在準(zhǔn)備Go語言面試,建議多做一些實際的并發(fā)編程練習(xí),掌握如何有效地使用Goroutine來處理高并發(fā)問題。
到此這篇關(guān)于go語言面試之Goroutine詳解的文章就介紹到這了,更多相關(guān)go Goroutine內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解如何使用go-acme/lego實現(xiàn)自動簽發(fā)證書
這篇文章主要為大家詳細介紹了如何使用?go-acme/lego?的客戶端或庫完成證書的自動簽發(fā),文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03golang連接mysql數(shù)據(jù)庫操作使用示例
這篇文章主要為大家介紹了golang連接mysql數(shù)據(jù)庫操作使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04golang 如何用反射reflect操作結(jié)構(gòu)體
這篇文章主要介紹了golang 用反射reflect操作結(jié)構(gòu)體的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04go語言for循環(huán)中嵌套defer的執(zhí)行順序
在Go語言中,defer語句用于延遲函數(shù)調(diào)用的執(zhí)行,本文主要介紹了go語言for循環(huán)中嵌套defer的執(zhí)行順序,具有一定的參考價值,感興趣的可以了解一下2025-03-03Go調(diào)度器學(xué)習(xí)之協(xié)作與搶占詳解
如果某個G執(zhí)行時間過長,其他的G如何才能被正常調(diào)度,這就引出了接下來的話題:協(xié)作與搶占。本文將通過一些示例為大家詳細講講調(diào)度器中協(xié)作與搶占的相關(guān)知識,需要的可以參考一下2023-04-04