Go defer與time.sleep的使用與區(qū)別
請大家看下面這段代碼,看運行結果會出現(xiàn)什么,為什么?
問題
demo
package main import ( "log" "time" ) func main() { start := time.Now() defer func() { log.Printf("匿名函數(shù)時間差: %v", time.Since(start)) }() defer log.Printf("時間差: %v", time.Since(start)) time.Sleep(3 * time.Second) log.Printf("函數(shù)結束") }
這里不少同學會認為:這題我會,先輸出
函數(shù)結束
,待整個函數(shù)結束后,根據(jù)defer后進先出
的順序依次打印計算的時間差,由于這里睡眠了3秒
,兩個時間差應該都是3秒左右
。
所以答案為:
函數(shù)結束
時間差:3s
匿名函數(shù)時間差:3s
看到這里,我想說同學你的思路和想法是好的,一開始我也和你一樣,但是這里的答案是錯的,那為什么錯呢?總得有個原因吧!很明顯匿名函數(shù)的時間差符合邏輯是對的,那為什么時間差與預期不符合呢?下面我來進行分析。
運行結果
分析
這里的關鍵點在于為什么時間差
為0
,也就是說為什么時間差
這個defer
語句沒有被time.sleep
所影響?進一步分析,就是查看start
的賦值時機在哪?是在一開始調(diào)用defer
就賦值,還是說在函數(shù)結束后給defer
中的start
賦值,從而造成結果的不同。
帶著這個問題,不妨來debug一下,看一下函數(shù)語句的執(zhí)行順序。
設置斷點
要想看一下start
的賦值時機,設置斷點在出現(xiàn)start
變量前面即可。
設置好斷點后,開始進行debug
debug查看
step1
一開始,先初始化start
的值
step2
接著,來到第一個defer
+ 匿名函數(shù)
,看它有沒有進去里面的printf
語句給start
變量進行賦值,step over
直接跳過了匿名函數(shù)
的defer
語句,也就是說明并沒有給匿名函數(shù)
中的defer
語句中的start
賦值
step3
之后,進入defer
語句中,給time.Since
中的start
進行賦值。
這里,會發(fā)現(xiàn)問題的關鍵所在,對比defer
+ 匿名函數(shù)
一開始調(diào)用(非執(zhí)行)時,不會對里面的變量(參數(shù))start
進行賦值。
然而普通的defer
+ printf
則在一開始時就會對里面的變量start
賦值,賦值后不會先把time.since
的結果計算出來,會在defer
調(diào)用后,也就是光標移動到下一條語句時調(diào)用time.sleep
計算時間差
,其結果就是0s
左右,此時不會在調(diào)用時輸出,待整個函數(shù)執(zhí)行完畢退出后,依次按照defer
順序輸出。
step4
進一步來到了time.sleep
,這也說明step3
確實給defer
語句賦值了,并沒有跳過,現(xiàn)在開始休眠3
s, 觀察3
s后會光標會去到哪里?
猜想:3s
后會先輸出函數(shù)結束
,之后函數(shù)退出,開始執(zhí)行defer
語句.
結合匿名函數(shù)的時間差為3s
左右,又因為defer
+匿名函數(shù)
中的start
還未賦值,會回到開頭的defer
+ 匿名函數(shù)
進行賦值。
等待3s鐘
step5
3s
后,來到了輸出函數(shù)結束
的語句。
進一步函數(shù)退出,也就是整個函數(shù)執(zhí)行完畢!
step6
又回到了剛才的defer
+ 匿名函數(shù)
果然與step4
的猜想一致!
關鍵點來了!如下圖:這里會進入defer
+ 匿名函數(shù)
, 并給里面的start
變量賦值。
注意,這里傳參start
還是一開始的start
! 只不過time.since(start)
的time
增加(休眠)了3s
,這也很好的解釋了為什么defer
+ 匿名函數(shù) 輸出的是3s
左右,而defer
+ printf
語句卻是輸出0s
左右。
之后匿名函數(shù)結束
退出當前的整個函數(shù)
最后整個函數(shù)結束,輸出debug
的結果:這里加了一些debug
調(diào)試的時間,以運行結果為準,見下。
運行結果如下:
探討
在debug
后,我們來探討一下為什么會出現(xiàn)這樣的情況?為什么結果會有所不同?里面的機制是什么?
defer + 輸出語句
傳值時機:一開始調(diào)用defer
時傳入
Go 語言中所有的函數(shù)調(diào)用都是傳值的
雖然 defer
是關鍵字,但是也繼承了這個特性。假設我們想要計算 main 函數(shù)運行的時間,可能會寫出以下的代碼:
package main import ( "fmt" "time" ) func main() { start := time.Now() // 這里誤以為:startedAt是在time.Sleep之后才會將參數(shù)傳遞給defer所在語句的函數(shù)中 defer fmt.Println(time.Since(start)) time.Sleep(3 * time.Second) }
關鍵點:調(diào)用defer
關鍵字會立刻拷貝函數(shù)中引用的外部參數(shù)
所以 time.Since(start)
的結果不是在 main 函數(shù)退出之前計算的,而是在 defer
關鍵字調(diào)用時賦值計算的,最終導致上述代碼輸出 0s
。
defer + 匿名函數(shù)
傳值時機:main函數(shù)結束后,執(zhí)行defer
函數(shù) 時傳入,傳入函數(shù)指針
package main import ( "fmt" "time" ) func main() { start := time.Now() // 使用匿名函數(shù),傳遞的是函數(shù)的指針 defer func() { fmt.Println(time.Since(start)) }() time.Sleep(3 * time.Second) }
那為什么使用匿名函數(shù)就可以輸出3s
呢?關鍵點:defer
使用匿名函數(shù) , 傳遞的是函數(shù)的指針(函數(shù)是一種指針類型),結合剛才的斷點分析,不會在一開始調(diào)用defer
+ 匿名函數(shù)
的時候就直接賦值,而是在后面main
函數(shù)退出時,再執(zhí)行defer
+匿名函數(shù)
時再傳入函數(shù)的指針,并給start
變量賦值。
總結
總而言之:一開始調(diào)用defer+輸出語句
會進行傳值
,會在調(diào)用defer
的時候就直接傳遞值的拷貝(如剛才的defer+輸出語句)
,從而計算時間差。
調(diào)用defer + 匿名函數(shù)
傳遞的是指針類型(如函數(shù)、匿名函數(shù)),
會在main
函數(shù)退出時,在執(zhí)行defer
+匿名函數(shù)
時再傳入函數(shù)的指針,并給里面的變量賦值。
歸根結底,是傳入的類型不同,繼而導致傳入變量的時機不同,造成輸出的結果不同!
擴展
面試官:請你用defer
與time.sleep
寫一個計算函數(shù)運行時間
的程序
相信看到這里的小伙伴,已經(jīng)很清楚要寫什么代碼了!
package main import ( "fmt" "time" ) func main() { start := time.Now() // 使用匿名函數(shù),傳遞的是函數(shù)的指針 defer func() { fmt.Println(time.Since(start)) }() time.Sleep(3 * time.Second) }
到此這篇關于Go defer與time.sleep的使用與區(qū)別的文章就介紹到這了,更多相關Go defer與time.sleep內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
淺析go語言如何實現(xiàn)協(xié)程的搶占式調(diào)度的
go語言通過GMP模型實現(xiàn)協(xié)程并發(fā),為了避免單協(xié)程持續(xù)持有線程導致線程隊列中的其他協(xié)程饑餓問題,設計者提出了一個搶占式調(diào)度機制,本文會基于一個簡單的代碼示例對搶占式調(diào)度過程進行深入講解剖析2024-04-04golang?pprof監(jiān)控memory?block?mutex統(tǒng)計原理分析
這篇文章主要為大家介紹了golang?pprof監(jiān)控memory?block?mutex統(tǒng)計原理分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04