此篇文章將介紹協(xié)程和延遲函數(shù)調(diào)用。協(xié)程和延遲函數(shù)調(diào)用是Go中比較獨(dú)特的兩個(gè)特性。 恐慌和恢復(fù)也將在此篇文章中得到簡(jiǎn)單介紹。本文并非全面地對(duì)這些特性進(jìn)行介紹,后面的其它文章會(huì)陸續(xù)補(bǔ)全本文的未介紹的內(nèi)容。
現(xiàn)代CPU一般含有多個(gè)核,并且一個(gè)核可能支持多線程。換句話說(shuō),現(xiàn)代CPU可以同時(shí)執(zhí)行多條指令流水線。 為了將CPU的能力發(fā)揮到極致,我們常常需要使我們的程序支持并發(fā)(concurrent)計(jì)算。
并發(fā)計(jì)算是指若干計(jì)算可能在某些時(shí)間片段內(nèi)同時(shí)運(yùn)行的情形。 下面這兩張圖描繪了兩種并發(fā)計(jì)算的場(chǎng)景。在此圖中,A和B表示兩個(gè)計(jì)算。 在第一種情形中,兩個(gè)計(jì)算只在某些時(shí)間片段同時(shí)運(yùn)行。 第二種情形稱為并行(parallel)計(jì)算。在并行計(jì)算中,多個(gè)計(jì)算在任何時(shí)間點(diǎn)都在同時(shí)運(yùn)行。并行計(jì)算屬于特殊的并發(fā)計(jì)算。
并發(fā)計(jì)算可能發(fā)生在同一個(gè)程序中、同一臺(tái)電腦上、或者同一個(gè)網(wǎng)絡(luò)中。 在《Go語(yǔ)言101》中,我們只談及發(fā)生在同一個(gè)程序中的并發(fā)計(jì)算。 在Go編程中,協(xié)程是創(chuàng)建計(jì)算的唯一途徑。
協(xié)程有時(shí)也被稱為綠色線程。綠色線程是由程序的運(yùn)行時(shí)(runtime)維護(hù)的線程。一個(gè)綠色線程的內(nèi)存開(kāi)銷(xiāo)和情景轉(zhuǎn)換(context switching)時(shí)耗比一個(gè)系統(tǒng)線程常常小得多。 只要內(nèi)存充足,一個(gè)程序可以輕松支持上萬(wàn)個(gè)并發(fā)協(xié)程。
Go不支持創(chuàng)建系統(tǒng)線程,所以協(xié)程是一個(gè)Go程序內(nèi)部唯一的并發(fā)實(shí)現(xiàn)方式。
每個(gè)Go程序啟動(dòng)的時(shí)候只有一個(gè)對(duì)用戶可見(jiàn)的協(xié)程,我們稱之為主協(xié)程。 一個(gè)協(xié)程可以開(kāi)啟更多其它新的協(xié)程。在Go中,開(kāi)啟一個(gè)新的協(xié)程是非常簡(jiǎn)單的。 我們只需在一個(gè)函數(shù)調(diào)用之前使用一個(gè)go關(guān)鍵字,即可讓此函數(shù)調(diào)用運(yùn)行在一個(gè)新的協(xié)程之中。 當(dāng)此函數(shù)調(diào)用退出后,這個(gè)新的協(xié)程也隨之結(jié)束了。我們可以稱此函數(shù)調(diào)用為一個(gè)協(xié)程調(diào)用(或者為此協(xié)程的啟動(dòng)調(diào)用)。 一個(gè)協(xié)程調(diào)用的所有返回值(如果存在的話)必須被全部舍棄。
在下面的例子程序中,主協(xié)程創(chuàng)建了兩個(gè)新的協(xié)程。在此例中,time.Duration是一個(gè)在time標(biāo)準(zhǔn)庫(kù)包中定義的類型。 此類型的底層類型為內(nèi)置類型int64。 底層類型這個(gè)概念將在下一篇文章中介紹。
package main
import (
"log"
"math/rand"
"time"
)
func SayGreetings(greeting string, times int) {
for i := 0; i < times; i++ {
log.Println(greeting)
d := time.Second * time.Duration(rand.Intn(5)) / 2
time.Sleep(d) // 睡眠片刻(隨機(jī)0到2.5秒)
}
}
func main() {
rand.Seed(time.Now().UnixNano())
log.SetFlags(0)
go SayGreetings("hi!", 10)
go SayGreetings("hello!", 10)
time.Sleep(2 * time.Second)
}
非常簡(jiǎn)單!我們編寫(xiě)了一個(gè)并發(fā)程序! 此程序在運(yùn)行的時(shí)候在某一時(shí)刻將很可能會(huì)有三個(gè)協(xié)程并存。 運(yùn)行之,可能會(huì)得到如下的結(jié)果(也可能是其它結(jié)果):
hi!
hello!
hello!
hello!
hello!
hi!
當(dāng)一個(gè)程序的主協(xié)程退出后,此程序也就退出了,即使還有一些其它協(xié)程在運(yùn)行。
和前面的幾篇文章不同,上面的例子程序使用了?log
?標(biāo)準(zhǔn)庫(kù)而不是?fmt
?標(biāo)準(zhǔn)庫(kù)中的?Println
?函數(shù)。 原因是?log
?標(biāo)準(zhǔn)庫(kù)中的打印函數(shù)是經(jīng)過(guò)了同步處理的(下一節(jié)將解釋什么是并發(fā)同步),而?fmt
?標(biāo)準(zhǔn)庫(kù)中的打印函數(shù)卻沒(méi)有被同步。 如果我們?cè)谏侠惺褂?fmt
?標(biāo)準(zhǔn)庫(kù)中的?Println
?函數(shù),則不同協(xié)程的打印可能會(huì)交織在一起。(雖然對(duì)此例來(lái)說(shuō),交織的概率很低。)
不同的并發(fā)計(jì)算可能共享一些資源,其中共享內(nèi)存資源最為常見(jiàn)。 在一個(gè)并發(fā)程序中,常常會(huì)發(fā)生下面的情形:
這些情形被稱為數(shù)據(jù)競(jìng)爭(zhēng)(data race)。并發(fā)編程的一大任務(wù)就是要調(diào)度不同計(jì)算,控制它們對(duì)資源的訪問(wèn)時(shí)段,以使數(shù)據(jù)競(jìng)爭(zhēng)的情況不會(huì)發(fā)生。 此任務(wù)常稱為并發(fā)同步(或者數(shù)據(jù)同步)。Go支持幾種并發(fā)同步技術(shù),這些并發(fā)同步技術(shù)將在后面的章節(jié)中逐一介紹。
并發(fā)編程中的其它任務(wù)包括:
上一節(jié)中這個(gè)并發(fā)程序是有缺陷的。我們本期望每個(gè)新創(chuàng)建的協(xié)程打印出10條問(wèn)候語(yǔ),但是主協(xié)程(和程序)在這20條問(wèn)候語(yǔ)還未都打印出來(lái)的時(shí)候就退出了。 如何確保主協(xié)程在這20條問(wèn)候語(yǔ)都打印完畢之后才退出呢?我們必須使用某種并發(fā)同步技術(shù)來(lái)達(dá)成這一目標(biāo)。
Go支持幾種并發(fā)同步技術(shù)。 其中, 通道是最獨(dú)特和最常用的。 但是,為了簡(jiǎn)單起見(jiàn),這里我們將使用?sync
?標(biāo)準(zhǔn)庫(kù)包中的?WaitGroup
?來(lái)同步上面這個(gè)程序中的主協(xié)程和兩個(gè)新創(chuàng)建的協(xié)程。
?WaitGroup
?類型有三個(gè)方法(特殊的函數(shù),將在以后的文章中詳解):?Add
?、?Done
?和?Wait
?。此類型將在后面的某篇文章中詳細(xì)解釋,目前我們可以簡(jiǎn)單地認(rèn)為:
Add
?方法用來(lái)注冊(cè)新的需要完成的任務(wù)數(shù)。Done
?方法用來(lái)通知某個(gè)任務(wù)已經(jīng)完成了。Wait
?方法調(diào)用將阻塞(等待)到所有任務(wù)都已經(jīng)完成之后才繼續(xù)執(zhí)行其后的語(yǔ)句。示例:
package main
import (
"log"
"math/rand"
"time"
"sync"
)
var wg sync.WaitGroup
func SayGreetings(greeting string, times int) {
for i := 0; i < times; i++ {
log.Println(greeting)
d := time.Second * time.Duration(rand.Intn(5)) / 2
time.Sleep(d)
}
wg.Done() // 通知當(dāng)前任務(wù)已經(jīng)完成。
}
func main() {
rand.Seed(time.Now().UnixNano())
log.SetFlags(0)
wg.Add(2) // 注冊(cè)兩個(gè)新任務(wù)。
go SayGreetings("hi!", 10)
go SayGreetings("hello!", 10)
wg.Wait() // 阻塞在這里,直到所有任務(wù)都已完成。
}
運(yùn)行這個(gè)修改后的程序,我們將會(huì)發(fā)現(xiàn)所有的20條問(wèn)候語(yǔ)都將在程序退出之前打印出來(lái)。
從上面這個(gè)的例子,我們可以看到一個(gè)活動(dòng)中的協(xié)程可以處于兩個(gè)狀態(tài):運(yùn)行狀態(tài)和阻塞狀態(tài)。一個(gè)協(xié)程可以在這兩個(gè)狀態(tài)之間切換。 比如上例中的主協(xié)程在調(diào)用?wg.Wait
?方法的時(shí)候,將從運(yùn)行狀態(tài)切換到阻塞狀態(tài);當(dāng)兩個(gè)新協(xié)程完成各自的任務(wù)后,主協(xié)程將從阻塞狀態(tài)切換回運(yùn)行狀態(tài)。
下面的圖片顯示了一個(gè)協(xié)程的生命周期。
注意,一個(gè)處于睡眠中的(通過(guò)調(diào)用?time.Sleep
?)或者在等待系統(tǒng)調(diào)用返回的協(xié)程被認(rèn)為是處于運(yùn)行狀態(tài),而不是阻塞狀態(tài)。
當(dāng)一個(gè)新協(xié)程被創(chuàng)建的時(shí)候,它將自動(dòng)進(jìn)入運(yùn)行狀態(tài),一個(gè)協(xié)程只能從運(yùn)行狀態(tài)而不能從阻塞狀態(tài)退出。 如果因?yàn)槟撤N原因而導(dǎo)致某個(gè)協(xié)程一直處于阻塞狀態(tài),則此協(xié)程將永遠(yuǎn)不會(huì)退出。 除了極個(gè)別的應(yīng)用場(chǎng)景,在編程時(shí)我們應(yīng)該盡量避免出現(xiàn)這樣的情形。
一個(gè)處于阻塞狀態(tài)的協(xié)程不會(huì)自發(fā)結(jié)束阻塞狀態(tài),它必須被另外一個(gè)協(xié)程通過(guò)某種并發(fā)同步方法來(lái)被動(dòng)地結(jié)束阻塞狀態(tài)。 如果一個(gè)運(yùn)行中的程序當(dāng)前所有的協(xié)程都出于阻塞狀態(tài),則這些協(xié)程將永遠(yuǎn)阻塞下去,程序?qū)⒈灰暈樗梨i了。 當(dāng)一個(gè)程序死鎖后,官方標(biāo)準(zhǔn)編譯器的處理是讓這個(gè)程序崩潰。
比如下面這個(gè)程序?qū)⒃谶\(yùn)行兩秒鐘后崩潰。
package main
import (
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
wg.Add(1)
go func() {
time.Sleep(time.Second * 2)
wg.Wait() // 阻塞在此
}()
wg.Wait() // 阻塞在此
}
它的輸出:
fatal error: all goroutines are asleep - deadlock!
...
以后我們將學(xué)習(xí)到更多可以讓一個(gè)協(xié)程進(jìn)入到阻塞狀態(tài)的操作。
并非所有處于運(yùn)行狀態(tài)的協(xié)程都在執(zhí)行。在任一時(shí)刻,只能最多有和邏輯CPU數(shù)目一樣多的協(xié)程在同時(shí)執(zhí)行。 我們可以調(diào)用runtime.NumCPU函數(shù)來(lái)查詢當(dāng)前程序可利用的邏輯CPU數(shù)目。 每個(gè)邏輯CPU在同一時(shí)刻只能最多執(zhí)行一個(gè)協(xié)程。Go運(yùn)行時(shí)(runtime)必須讓邏輯CPU頻繁地在不同的處于運(yùn)行狀態(tài)的協(xié)程之間切換,從而每個(gè)處于運(yùn)行狀態(tài)的協(xié)程都有機(jī)會(huì)得到執(zhí)行。 這和操作系統(tǒng)執(zhí)行系統(tǒng)線程的原理是一樣的。
下面這張圖顯示了一個(gè)協(xié)程的更詳細(xì)的生命周期。在此圖中,運(yùn)行狀態(tài)被細(xì)分成了多個(gè)子狀態(tài)。 一個(gè)處于排隊(duì)子狀態(tài)的協(xié)程等待著進(jìn)入執(zhí)行子狀態(tài)。一個(gè)處于執(zhí)行子狀態(tài)的協(xié)程在被執(zhí)行一會(huì)兒(非常短的時(shí)間片)之后將進(jìn)入排隊(duì)子狀態(tài)。
請(qǐng)注意,為了解釋的簡(jiǎn)單性,在以后其它的《Go語(yǔ)言101》文章中,上圖中所示的子狀態(tài)將不會(huì)再提及。 重申一下,睡眠和等待系統(tǒng)調(diào)用返回子狀態(tài)被認(rèn)為是運(yùn)行狀態(tài),而不是阻塞狀態(tài)。
標(biāo)準(zhǔn)編譯器采納了一種被稱為M-P-G模型的算法來(lái)實(shí)現(xiàn)協(xié)程調(diào)度。 其中,M表示系統(tǒng)線程,P表示邏輯處理器(并非上述的邏輯CPU),G表示協(xié)程。 大多數(shù)的調(diào)度工作是通過(guò)邏輯處理器(P)來(lái)完成的。 邏輯處理器像一個(gè)監(jiān)工一樣通過(guò)將不同的處于運(yùn)行狀態(tài)協(xié)程(G)交給不同的系統(tǒng)線程(M)來(lái)執(zhí)行。 一個(gè)協(xié)程在同一時(shí)刻只能在一個(gè)系統(tǒng)線程中執(zhí)行。一個(gè)執(zhí)行中的協(xié)程運(yùn)行片刻后將自發(fā)地脫離讓出一個(gè)系統(tǒng)線程,從而使得其它處于等待子狀態(tài)的協(xié)程得到執(zhí)行機(jī)會(huì)。
在運(yùn)行時(shí)刻,我們可以調(diào)用runtime.GOMAXPROCS函數(shù)來(lái)獲取和設(shè)置邏輯處理器的數(shù)量。 對(duì)于官方標(biāo)準(zhǔn)編譯器,在Go 1.5之前,默認(rèn)初始邏輯處理器的數(shù)量為1;自從Go 1.5之后,默認(rèn)初始邏輯處理器的數(shù)量和邏輯CPU的數(shù)量一致。 此新的默認(rèn)設(shè)置在大多數(shù)情況下是最佳選擇。但是對(duì)于某些文件操作十分頻繁的程序,設(shè)置一個(gè)大于?runtime.NumCPU()
?的?GOMAXPROCS
?值可能是有好處的。
我們也可以通過(guò)設(shè)置?GOMAXPROCS
?環(huán)境變量來(lái)設(shè)置一個(gè)Go程序的初始邏輯處理器數(shù)量。
在Go中,一個(gè)函數(shù)調(diào)用可以跟在一個(gè)?defer
?關(guān)鍵字后面,成為一個(gè)延遲函數(shù)調(diào)用。 此?defer
?關(guān)鍵字和此延遲函數(shù)調(diào)用一起形成一個(gè)延遲調(diào)用語(yǔ)句。 和協(xié)程調(diào)用類似,被延遲的函數(shù)調(diào)用的所有返回值(如果存在)必須全部被舍棄。
當(dāng)一個(gè)延遲調(diào)用語(yǔ)句被執(zhí)行時(shí),其中的延遲函數(shù)調(diào)用不會(huì)立即被執(zhí)行,而是被推入由當(dāng)前協(xié)程維護(hù)的一個(gè)延遲調(diào)用隊(duì)列(一個(gè)后進(jìn)先出隊(duì)列)。 當(dāng)一個(gè)函數(shù)調(diào)用返回(此時(shí)可能尚未完全退出)并進(jìn)入它的退出階段后,所有在執(zhí)行此函數(shù)調(diào)用的過(guò)程中已經(jīng)被推入延遲調(diào)用隊(duì)列的調(diào)用將被按照它們被推入的順序逆序被彈出隊(duì)列并執(zhí)行。 當(dāng)所有這些延遲調(diào)用執(zhí)行完畢后,此函數(shù)調(diào)用也就完全退出了。
下面這個(gè)例子展示了如何使用延遲調(diào)用函數(shù)。
package main
import "fmt"
func main() {
defer fmt.Println("The third line.")
defer fmt.Println("The second line.")
fmt.Println("The first line.")
}
輸出結(jié)果:
The first line.
The second line.
The third line.
下面是另一個(gè)略微復(fù)雜一點(diǎn)的使用了延遲調(diào)用的例子程序。此程序?qū)凑兆匀粩?shù)的順序打印出0到9十個(gè)數(shù)字。
package main
import "fmt"
func main() {
defer fmt.Println("9")
fmt.Println("0")
defer fmt.Println("8")
fmt.Println("1")
if false {
defer fmt.Println("not reachable")
}
defer func() {
defer fmt.Println("7")
fmt.Println("3")
defer func() {
fmt.Println("5")
fmt.Println("6")
}()
fmt.Println("4")
}()
fmt.Println("2")
return
defer fmt.Println("not reachable")
}
一個(gè)例子:
package main
import "fmt"
func Triple(n int) (r int) {
defer func() {
r += n // 修改返回值
}()
return n + n // <=> r = n + n; return
}
func main() {
fmt.Println(Triple(5)) // 15
}
事實(shí)上,上面的幾個(gè)使用了延遲函數(shù)調(diào)用的例子中的延遲函數(shù)調(diào)用并非絕對(duì)必要。 但是延遲調(diào)用對(duì)于下面將要介紹的恐慌/恢復(fù)特性是必要的。
另外延遲函數(shù)調(diào)用可以幫助我們寫(xiě)出更整潔和更魯棒的代碼。我們可以在后面的更多關(guān)于延遲調(diào)用一文中讀到這樣的例子。
一個(gè)延遲調(diào)用的實(shí)參是在此調(diào)用對(duì)應(yīng)的延遲調(diào)用語(yǔ)句被執(zhí)行時(shí)被估值的。 或者說(shuō),它們是在此延遲調(diào)用被推入延遲調(diào)用隊(duì)列時(shí)被估值的。 這些被估值的結(jié)果將在以后此延遲調(diào)用被執(zhí)行的時(shí)候使用。
一個(gè)匿名函數(shù)體內(nèi)的表達(dá)式是在此函數(shù)被執(zhí)行的時(shí)候才會(huì)被逐漸估值的,不管此函數(shù)是被普通調(diào)用還是延遲/協(xié)程調(diào)用。
一個(gè)例子:
package main
import "fmt"
func main() {
func() {
for i := 0; i < 3; i++ {
defer fmt.Println("a:", i)
}
}()
fmt.Println()
func() {
for i := 0; i < 3; i++ {
defer func() {
fmt.Println("b:", i)
}()
}
}()
}
運(yùn)行之,將得到如下結(jié)果:
a: 2
a: 1
a: 0
b: 3
b: 3
b: 3
第一個(gè)匿名函數(shù)中的循環(huán)打印出?2
?、?1
?和?0
?這個(gè)序列,但是第二個(gè)匿名函數(shù)中的循環(huán)打印出三個(gè)?3
?。 因?yàn)榈谝粋€(gè)循環(huán)中的?i
?是在?fmt.Println
?函數(shù)調(diào)用被推入延遲調(diào)用隊(duì)列的時(shí)候估的值,而第二個(gè)循環(huán)中的?i
?是在第二個(gè)匿名函數(shù)調(diào)用的退出階段估的值(此時(shí)循環(huán)變量?i
?的值已經(jīng)變?yōu)?3
?)。
我們可以對(duì)第二個(gè)循環(huán)略加修改(使用兩種方法),使得它和第一個(gè)循環(huán)打印出相同的結(jié)果。
for i := 0; i < 3; i++ {
defer func(i int) {
// 此i為形參i,非實(shí)參循環(huán)變量i。
fmt.Println("b:", i)
}(i)
}
或者
for i := 0; i < 3; i++ {
i := i // 在下面的調(diào)用中,左i遮擋了右i。
// <=> var i = i
defer func() {
// 此i為上面的左i,非循環(huán)變量i。
fmt.Println("b:", i)
}()
}
同樣的估值時(shí)刻規(guī)則也適用于協(xié)程調(diào)用。下面這個(gè)例子程序?qū)⒋蛴〕?span style="background-color: rgb(249, 242, 244); color: rgb(199, 37, 78); font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: inherit; white-space: nowrap;">123 789。
package main
import "fmt"
import "time"
func main() {
var a = 123
go func(x int) {
time.Sleep(time.Second)
fmt.Println(x, a) // 123 789
}(a)
a = 789
time.Sleep(2 * time.Second)
}
順便說(shuō)一句,使用?time.Sleep
?調(diào)用來(lái)做并發(fā)同步不是一個(gè)好的方法。 如果上面這個(gè)程序運(yùn)行在一個(gè)滿負(fù)荷運(yùn)行的電腦上,此程序可能在新啟動(dòng)的協(xié)程可能還未得到執(zhí)行機(jī)會(huì)的時(shí)候就已經(jīng)退出了。 在正式的項(xiàng)目中,我們應(yīng)該使用并發(fā)同步技術(shù)一文中列出的方法來(lái)實(shí)現(xiàn)并發(fā)同步。
Go不支持異常拋出和捕獲,而是推薦使用返回值顯式返回錯(cuò)誤。 不過(guò),Go支持一套和異常拋出/捕獲類似的機(jī)制。此機(jī)制稱為恐慌/恢復(fù)(panic/recover)機(jī)制。
我們可以調(diào)用內(nèi)置函數(shù)?panic
?來(lái)產(chǎn)生一個(gè)恐慌以使當(dāng)前協(xié)程進(jìn)入恐慌狀況。
進(jìn)入恐慌狀況是另一種使當(dāng)前函數(shù)調(diào)用開(kāi)始返回的途徑。 一旦一個(gè)函數(shù)調(diào)用產(chǎn)生一個(gè)恐慌,此函數(shù)調(diào)用將立即進(jìn)入它的退出階段。
通過(guò)在一個(gè)延遲函數(shù)調(diào)用之中調(diào)用內(nèi)置函數(shù)?recover
?,當(dāng)前協(xié)程中的一個(gè)恐慌可以被消除,從而使得當(dāng)前協(xié)程重新進(jìn)入正常狀況。
如果一個(gè)協(xié)程在恐慌狀況下退出,它將使整個(gè)程序崩潰。
內(nèi)置函數(shù)panic和recover的聲明原型如下:
func panic(v interface{})
func recover() interface{}
接口(interface)類型和接口值將在以后的文章接口中詳解。 目前,我們可以暫時(shí)將空接口類型?interface{}
?視為很多其它語(yǔ)言中的?any
?或者?Object
?類型。 換句話說(shuō),在一個(gè)?panic
?函數(shù)調(diào)用中,我們可以傳任何實(shí)參值。
一個(gè)?recover
?函數(shù)的返回值為其所恢復(fù)的恐慌在產(chǎn)生時(shí)被一個(gè)?panic
?函數(shù)調(diào)用所消費(fèi)的參數(shù)。
下面這個(gè)例子展示了如何產(chǎn)生一個(gè)恐慌和如何消除一個(gè)恐慌。
package main
import "fmt"
func main() {
defer func() {
fmt.Println("正常退出")
}()
fmt.Println("嗨!")
defer func() {
v := recover()
fmt.Println("恐慌被恢復(fù)了:", v)
}()
panic("拜拜!") // 產(chǎn)生一個(gè)恐慌
fmt.Println("執(zhí)行不到這里")
}
它的輸出結(jié)果:
嗨!
恐慌被恢復(fù)了: 拜拜!
正常退出
下面的例子在一個(gè)新協(xié)程里面產(chǎn)生了一個(gè)恐慌,并且此協(xié)程在恐慌狀況下退出,所以整個(gè)程序崩潰了。
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("hi!")
go func() {
time.Sleep(time.Second)
panic(123)
}()
for {
time.Sleep(time.Second)
}
}
運(yùn)行之,輸出如下:
hi!
panic: 123
goroutine 5 [running]:
...
Go運(yùn)行時(shí)(runtime)會(huì)在若干情形下產(chǎn)生恐慌,比如一個(gè)整數(shù)被0除的時(shí)候。下面這個(gè)程序?qū)⒈罎⑼顺觥?/p>
package main
func main() {
a, b := 1, 0
_ = a/b
}
它的輸出:
panic: runtime error: integer divide by zero
goroutine 1 [running]:
...
一般說(shuō)來(lái),恐慌用來(lái)表示正常情況下不應(yīng)該發(fā)生的邏輯錯(cuò)誤。 如果這樣的一個(gè)錯(cuò)誤在運(yùn)行時(shí)刻發(fā)生了,則它肯定是由于某個(gè)bug引起的。 另一方面,非邏輯錯(cuò)誤是現(xiàn)實(shí)中難以避免的錯(cuò)誤,它們不應(yīng)該導(dǎo)致恐慌。 我們必須正確地對(duì)待和處理非邏輯錯(cuò)誤。
更多可能由Go運(yùn)行時(shí)產(chǎn)生的恐慌將在以后其它文章中提及。
以后,我們可以了解一些恐慌/恢復(fù)用例和更多關(guān)于恐慌/恢復(fù)機(jī)制的細(xì)節(jié)。
對(duì)于官方標(biāo)準(zhǔn)編譯器來(lái)說(shuō),很多致命性錯(cuò)誤(比如棧溢出和內(nèi)存不足)不能被恢復(fù)。它們一旦產(chǎn)生,程序?qū)⒈罎ⅰ?/p>
更多建議: