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

深入學(xué)習(xí)Go延遲語(yǔ)句

 更新時(shí)間:2025年06月09日 09:23:22   作者:JustGopher  
本文主要介紹了深入學(xué)習(xí)Go延遲語(yǔ)句,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

1 延遲語(yǔ)句是什么

編程的時(shí)候,經(jīng)常會(huì)需要申請(qǐng)一些資源,比如數(shù)據(jù)庫(kù)連接、文件、鎖等,這些資源需要再使用后釋放掉,否則會(huì)造成內(nèi)存泄露。但是編程人員經(jīng)常容易忘記釋放這些資源,從而造成一些事故。 Go 語(yǔ)言直接在語(yǔ)言層面提供 defer 關(guān)鍵字,在申請(qǐng)資源語(yǔ)句的下一行,可以直接用 defer 語(yǔ)句來(lái)注冊(cè)函數(shù)結(jié)束后執(zhí)行釋放資源的操作。因?yàn)檫@樣一顆小小的語(yǔ)法糖,忘關(guān)閉資源語(yǔ)句的情況就打打地減少了。

defer 是 Go 語(yǔ)言提供的一種用于注冊(cè)延遲調(diào)用的機(jī)制:讓函數(shù)或語(yǔ)句可以在當(dāng)前函數(shù)執(zhí)行完畢后(包括通過(guò) return 正常結(jié)束或者 panic 導(dǎo)致的異常結(jié)束)執(zhí)行。在需要釋放資源的場(chǎng)景非常有用,可以很方便在函數(shù)結(jié)束前做一些清理操作。在打開(kāi)資源語(yǔ)句的下一行,直接使用 defer 就可以在函數(shù)返回前釋放資源,可謂相當(dāng)?shù)母咝А?/p>

defer 通常用于一些成對(duì)操作的場(chǎng)景:打開(kāi)連接 / 關(guān)閉連接、加鎖 / 釋放鎖、打開(kāi)文件 / 關(guān)閉文件等。使用非常簡(jiǎn)單:

f, err := os.Open(filename)
if err !=  nil {
    panic(err)
}

if f !=  nil {
    defer f.Close()
}

在打開(kāi)文件的語(yǔ)句附近,用 defer 語(yǔ)句關(guān)閉文件。這樣,在函數(shù)結(jié)束之前,會(huì)自動(dòng)執(zhí)行 defer 后面的語(yǔ)句來(lái)關(guān)閉文件。注意,要先判斷 f 是否為空,如果 f 不為空,在調(diào)用 f.Close() 函數(shù),避免出現(xiàn)異常情況。

當(dāng)然, defer 會(huì)有短暫延遲,對(duì)時(shí)間要求特別高的程序,可以避免使用它,其他情況一般可以忽略它帶來(lái)的延遲。特別是 Go 1.14 又對(duì) defer 做了很大幅度的優(yōu)化,效率提升不少。

下面是一個(gè)反面例子:

r.mu.Lock()
rand.Intn(param)
r.mu.Unlock()

上面只有三行代碼,看起來(lái)這里不用 defer 執(zhí)行 Unlock 并沒(méi)有什么問(wèn)題。其實(shí)并不是這樣,中間這行代碼 rand.Intn(param) 其實(shí)是有可能發(fā)生 panic 的,更嚴(yán)重的情況是,這段代碼很有可能被其他人修改,增加更多的邏輯,而這完全不可控。也就是說(shuō),在 Lock 和 Unlock 之間的代碼一旦出現(xiàn)異常情況導(dǎo)致 panic ,就會(huì)形成死鎖。因此這里的邏輯是,即使看起來(lái)非常簡(jiǎn)單的代碼,使用 defer 也是有必要的,因?yàn)樾枨罂偸窃谧兓?,代碼也總會(huì)被修改。

2 延遲語(yǔ)句的執(zhí)行順序是什么

先看下官方文檔對(duì) defer 的解釋?zhuān)?/p>

Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. If a deferred function value evaluates to nil, execution panics when the function is invoked, not when the “defer” statement is executed.(每次 defer 語(yǔ)句執(zhí)行的時(shí)候,會(huì)把函數(shù) “壓棧”,函數(shù)參數(shù)會(huì)被復(fù)制下來(lái);當(dāng)外層函數(shù) (注意不是代碼塊,如一個(gè) for 循環(huán)塊并不是外層函數(shù)) 退出時(shí),defer 函數(shù)按照定義 的順序逆序執(zhí)行;如果 defer 執(zhí)行的函數(shù)為 nil,那么會(huì)在最終調(diào)用函數(shù)的時(shí)候產(chǎn)生 panic。)

defer 語(yǔ)句并不會(huì)馬上執(zhí)行,而是會(huì)進(jìn)入一個(gè)棧,函數(shù) return 前,會(huì)按照先進(jìn)后出的順序執(zhí)行。也就是一說(shuō),最先被定義的 defer 語(yǔ)句最后執(zhí)行。先進(jìn)后出的原因是后面定義的函數(shù)可能會(huì)依賴(lài)前面的資源,自然要先執(zhí)行;否則,如果前面先執(zhí)行了,那后面函數(shù)的依賴(lài)就沒(méi)有了,因而可能會(huì)出錯(cuò)。

在 defer 函數(shù)定義時(shí),對(duì)外部變量的引用有兩種方式:函數(shù)參數(shù)、閉包引用。前者再 defer 定義時(shí)就把值傳遞給 defer,并被 cache 起來(lái);后者則會(huì)在 defer 函數(shù)真正調(diào)用時(shí)根據(jù)整個(gè)上下文確定參數(shù)當(dāng)前的值。

defer 后面的函數(shù)在執(zhí)行的時(shí)候,函數(shù)調(diào)用的參數(shù)會(huì)被保存起來(lái),也就是復(fù)制一份。真正執(zhí)行的時(shí)候,實(shí)際上用到的是這個(gè)復(fù)制的變量,因此如果此變量是一個(gè)” 值 “,那么就和定義的是一直的。如果此變量是一個(gè)” 引用 “,那就可能和定義的不一致。

舉個(gè)例子:

func  main() {
    var v [3]struct{}
    for i :=  range v {
        defer  func() {
            fmt.Println(i)
        } ()
    }
}

執(zhí)行結(jié)果:

2
2
2

defer 后面跟的是一個(gè)閉包,i 是” 引用 “類(lèi)型的變量, for 循環(huán)結(jié)束后 i 的值為 2,因此最后打印了 3 個(gè) 2。

再看一個(gè)例子:

type  num  int

func (n num) print() {fmt.Println(n)}
func (n *num) pprint() {fmt.Println(*n)}
func  main() {
    var n num
    defer n.print()
    defer n.pprint()
    defer  func() {n.print()} ()
    defer  func() {n.pprint()} ()
    n =  3
}

執(zhí)行結(jié)果為:

3
3
3
0

注意,defer 語(yǔ)句的執(zhí)行順序和定義順序相反。

第四個(gè) defer 語(yǔ)句是閉包,引用外部函數(shù)的 n ,最終結(jié)果為 3;第三個(gè) defer 語(yǔ)句同上;第二個(gè) defer 語(yǔ)句,n 是引用,最終求值是 3;第一個(gè) defer 語(yǔ)句,對(duì) n 直接求值,開(kāi)始的時(shí)候是 0,所以最后是 0。

再看一個(gè)延伸例子:

func  main() {
    defer  func() {
        fmt.Println("before return")
    }()
    if  true {
        fmt.Println("during return")
        return
    }

    defer  func() {
        fmt.Println("after return")
    }()
}

運(yùn)行結(jié)果如下:

during return
before return

解析:return 之后的 defer 函數(shù)不能被注冊(cè),因此不能打印出 after return。

延伸示例則可以視為對(duì) defer 的原理的利用。某些情況下,會(huì)故意用到 defer 的” 先求值,在延遲調(diào)用 “的性質(zhì)。想象這樣一個(gè)場(chǎng)景:在一個(gè)函數(shù)里,需要打開(kāi)兩個(gè)文件進(jìn)行合并操作,合并完成后,在函數(shù)結(jié)束前關(guān)閉打開(kāi)的文件句柄:

func  mergeFile() error {
    // 打開(kāi)文件一
    f, _ := os.Open("file1.txt")
    if f !=  nil {
        defer  func(f  io.Closer) {
            if err := f.Close(); err !=nil {
                fmt.Printf("defer close file1.txt err %v\n", err)
            }
        }(f)
    }

    // 打開(kāi)文件二
    f, _ := os.Open("file2.txt")
    if f !=  nil {
        defer  func(f  io.Closer) {
            if err := f.Close(); err !=nil {
                fmt.Printf("defer close file2.txt err %v\n", err)
            }
        }(f)
    }
    // ......
    return  nil
}

上面的代碼中就用到了 defer 的原理,defer 函數(shù)定義的時(shí)候,參數(shù)就已經(jīng)復(fù)制進(jìn)去了,之后,真正執(zhí)行 close() 函數(shù)的時(shí)候就剛好關(guān)閉的是正確的” 文件 “了,很巧妙,如果不這樣,將 f 當(dāng)成函數(shù)參數(shù)傳遞進(jìn)去的話(huà),最后兩個(gè)語(yǔ)句關(guān)閉的就是同一個(gè)文件:都是最后打開(kāi)的文件。

在調(diào)用 close() 函數(shù)的時(shí)候,要注意一點(diǎn):先判斷調(diào)用主題是否為空,否則可能會(huì)解引用一個(gè)空指針,進(jìn)而 panic。

3 如何拆解延遲語(yǔ)句

如果 defer 像前面介紹的那樣簡(jiǎn)單,這個(gè)世界就完美了。但事情總是沒(méi)那么簡(jiǎn)單,defer 用得不好,會(huì)陷入泥潭。

避免陷入泥潭的關(guān)鍵是必須深刻理解下面這條語(yǔ)句:

return xxx

上面這條語(yǔ)句經(jīng)過(guò)編譯之后,實(shí)際上生成了三條指令:

  • 返回值 = xxx
  • 調(diào)用 defer 函數(shù)
  • 空的 return

第一步和第三步是 return 語(yǔ)句生成的指令,也就是說(shuō) return 并不是一條原子指令;第二步是 defer 定義的語(yǔ)句,這里可能會(huì)操作返回值,從而影響最終的結(jié)果。

看兩個(gè)例子,試著將 return 語(yǔ)句和 defer 語(yǔ)句拆解到正確的順序。

第一個(gè)例子:

func  f()(r  int) {
    t :=  5
    defer  func() {
        t = t +  5
    }()
    return t
}

拆解后:

func  f() (r  int) {
    t :=  5
    // 1. 賦值指令
    r = t
    // 2. defer 被插入到賦值與返回之間執(zhí)行,這個(gè)例子中返回值 r 沒(méi)被修改過(guò)
    func() {
        t = t +  5
    }
    // 3. 空的 return 指令
    return
}

這里第二步實(shí)際上并沒(méi)有操作返回值 r,因此,main 函數(shù)中調(diào)用 f() 得到 5。

第二個(gè)例子:

func  f()(r  int) {
    defer func(r  int) {
        r = r +  5
    }(r)
    return  1
}

拆解后:

func  f()(r  int) {
    // 1. 賦值
    r =  1
    // 2. 這里改的 r 是之前傳進(jìn)去的r,不會(huì)改變要返回的那個(gè) r 值
    defer func(r  int) {
        r = r +  5
    }(r)
    // 3. 空的 return
    return  1
}

第二步,改變的是傳值進(jìn)去的 r,是形參的一個(gè)復(fù)制值,不會(huì)影響實(shí)參 r。因此,main 函數(shù)中需要調(diào)用 f() 得到 1。

4 如何確定延遲語(yǔ)句的參數(shù)

defer 語(yǔ)句表達(dá)式的值在定義時(shí)就已經(jīng)確定了。下面可以通過(guò)三個(gè)不同的函數(shù)來(lái)理解:

func  f1() {
    var err error
    defer fmt.Println(err)
    err = errors.New("defer1 error")
    return
}

func  f2() {
    var err error
    defer func() {
        fmt.Println(err)
    }()
    err = errors.New("defer2 error")
    return
}

func  f3() {
    var err error
    defer func(err  error) {
        fmt.Println(err)
    }(err)
    err = errors.New("defer3 error")
    return
}

func  main() {
    f1()
    f2()
    f3()
}

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

<nil>
defer2 error
<nil>

第一和第三個(gè)函數(shù)中,因?yàn)樽鳛閰?shù),err 在函數(shù)定義的時(shí)候就會(huì)求值,并且定義的時(shí)候 err 的值都是 nil,所以最后打印的結(jié)果都是 nil;第二個(gè)函數(shù)的參數(shù)其實(shí)也會(huì)在定義的時(shí)候求值,但是第二個(gè)例子中是一個(gè)閉包,它引用的變量 err 在執(zhí)行的時(shí)候值最終變成 defer2 error 了。

現(xiàn)實(shí)中第三個(gè)函數(shù)比較容易犯錯(cuò)誤,在生產(chǎn)環(huán)境中,很容易寫(xiě)出這樣的錯(cuò)誤代碼,導(dǎo)致最后 defer 語(yǔ)句沒(méi)有起到作用,造成一些線上事故,要特別注意。

5 閉包是什么

閉包不是一句兩句話(huà)可以說(shuō)清楚的,大家感興趣的話(huà)可以自行搜索這塊知識(shí)點(diǎn),可以參考 閉包 和 閉包 這部分內(nèi)容自己研究。

閉包是由函數(shù)及其相關(guān)引用環(huán)境組合而成的實(shí)體,即:閉包 = 函數(shù) + 引用環(huán)境。

一般的函數(shù)都有函數(shù)名,而匿名函數(shù)沒(méi)有。匿名函數(shù)不能獨(dú)立存在,但可以直接調(diào)用或者賦值于某個(gè)變量。匿名函數(shù)也被稱(chēng)為閉包,一個(gè)閉包繼承了函數(shù)聲明時(shí)的作用域。在 Go 語(yǔ)言中,所有的匿名函數(shù)都是閉包。

有個(gè)不太恰當(dāng)?shù)睦樱嚎梢园验]包看成是一個(gè)類(lèi),一個(gè)閉包函數(shù)調(diào)用就是實(shí)例化一個(gè)類(lèi)。閉包在運(yùn)行時(shí)可以有多個(gè)實(shí)例,它會(huì)將同一個(gè)作用域里的變量和常量捕獲下來(lái),無(wú)論閉包在什么地方被調(diào)用 (實(shí)例化) 時(shí),都可以使用這些變量和常量。而且,閉包捕獲的變量和常量是引用傳遞,不是值傳遞。

舉個(gè)例子:

func  main() {
    var a =  Accumulator()
    fmt.Printf("%d\n", a(1))
    fmt.Printf("%d\n", a(10))
    fmt.Printf("%d\n", a(100))
    fmt.Println("------------------------")
    var b =  Accumulator()
    fmt.Printf("%d\n", b(1))
    fmt.Printf("%d\n", b(10))
    fmt.Printf("%d\n", b(100))
}

func Accumulator() func(int) int {
    var x int
    return func(delta  int) int {
        fmt.Printf("(%+v, %+v) - ", &x, x)
        x += delta
        return x
    }
}

執(zhí)行結(jié)果是:

(0xc420014070, 0) - 1
(0xc420014070, 1) - 11
(0xc420014070, 11) - 111
------------------------
(0xc4200140b8, 0) - 1
(0xc4200140b8, 1) - 11
(0xc4200140b8, 11) – 111

閉包引用了 x 變量,a,b 可看作 2 個(gè)不同的實(shí)例,實(shí)例之間互不影響。實(shí)例內(nèi)部,x 變量是同一個(gè)地址,因此具有 “累加效應(yīng)”。

6 延遲語(yǔ)句如何配合恢復(fù)語(yǔ)句

Go 語(yǔ)言被詬病多次的就是它的 error,實(shí)際項(xiàng)目里經(jīng)常出現(xiàn)各種 error 滿(mǎn)天飛,正常的代碼邏輯里有很多 error 處理的代碼塊。函數(shù)總是會(huì)返回一個(gè) error,留給調(diào)用者處理;而如果是致命的錯(cuò)誤,比如程序執(zhí)行初始化的時(shí)候出問(wèn)題,最好直接 panic 掉,避免上線運(yùn)行后出更大的問(wèn)題。

有些時(shí)候,需要從異常中恢復(fù)。比如服務(wù)器程序遇到嚴(yán)重問(wèn)題,產(chǎn)生了 panic,這時(shí)至少可以在程序崩潰前做一些 “掃尾工作”,比如關(guān)閉客戶(hù)端的連接,防止客戶(hù)端一直等待等;并且單個(gè)請(qǐng)求導(dǎo)致的 panic,也不應(yīng)該影響整個(gè)服務(wù)器程序的運(yùn)行。

panic 會(huì)停掉當(dāng)前正在執(zhí)行的程序,而不只是當(dāng)前線程。在這之前,它會(huì)有序地執(zhí)行完當(dāng)前線程 defer 列表里的語(yǔ)句,其他協(xié)程里定義的 defer 語(yǔ)句不作保證。所以在 defer 里定義一個(gè) recover 語(yǔ)句,防止程序直接掛掉,就可以起到類(lèi)似 Java 里 try…catch 的效果。

注意,recover() 函數(shù)只在 defer 的函數(shù)中直接調(diào)用才有效。例如:

func  main() {
    defer fmt.Println("defer main")
    var user = os.Getenv("USER_")
    go  func() {
        defer  func() {
            fmt.Println("defer caller")
            if err :=  recover(); err !=  nil {
                fmt.Println("recover success, err: ", err)
            }
        }()

        func() {
            defer  func() {
                fmt.Println("defer here")
            }()
            if user ==  "" {
                panic("should set user env")
            }
            fmt.Println("after panic")
        }()
    }()
    time.Sleep(100)
    fmt.Println("end of main function")
}

執(zhí)行結(jié)果:

defer here
defer caller
recover success. err: should set user env.
end of main function
defer main

代碼中的 panic 最終會(huì)被 recover 捕獲到。這樣的處理方式在一個(gè) http server 的主流程常常會(huì)被用到。一次偶然的請(qǐng)求可能會(huì)觸發(fā)某個(gè) bug,這時(shí)用 recover 捕獲 panic,穩(wěn)住主流程,不影響其他請(qǐng)求。

再看幾個(gè)延伸的示例。這些例子都與 recover() 函數(shù)的調(diào)用位置有關(guān)。

考慮一下寫(xiě)法,程序是否能正確 recover 嗎?如果不能,原因是什么:

第一個(gè)例子:

func  main() {
    defer  f()
    panic(404)
}

func  f() {
    if e :=  recover(); err !=  nil {
        fmt.Println("recover")
        return
    }
}

能,在 defer 函數(shù)中調(diào)用,生效。

第二個(gè)例子:

func  main() {
    recover()
    panic(404)
}

不能。直接調(diào)用 recover,返回 nil。

第三個(gè)例子:

func  main() {
    defer  recover()
    panic(404)
}

不能。要在 defer 函數(shù)中調(diào)用 recover。

第四個(gè)例子:

func  main() {
    defer  func() {
        if e :=  recover(); e !=  nil {
            fmt.Println(err)
        }
    }()
    panic(404)
}

能,在 defer 函數(shù)中調(diào)用,生效。

第五個(gè)例子:

func  main() {
    defer  func() {
        defer  func() {
            recover()
        }()
    }()
    panic()
}

不能,多重 defer 嵌套。

7 defer 鏈如果被遍歷執(zhí)行

為了在退出函數(shù)前執(zhí)行一些資源清理的操作,例如關(guān)閉文件、釋放連接、釋放鎖資源等,會(huì)在函數(shù)里寫(xiě)上多個(gè) defer 語(yǔ)句,被 defered 的函數(shù),以 “先進(jìn)后出” 的順序,在 RET 指令前得以執(zhí)行。

在一條函數(shù)調(diào)用鏈中,多個(gè)函數(shù)中會(huì)出現(xiàn)多個(gè) defer 語(yǔ)句。例如:a()→b()→c() 中,每個(gè)函數(shù)里都有 defer 語(yǔ)句,而這些 defer 語(yǔ)句會(huì)創(chuàng)建對(duì)應(yīng)個(gè)數(shù)的 _defer 結(jié)構(gòu)體,這些結(jié)構(gòu)體以鏈表的形式 “掛” 在 G 結(jié)構(gòu)體下。

多個(gè) _defer 結(jié)構(gòu)體形成一個(gè)鏈表,G 結(jié)構(gòu)體中某個(gè)字段指向此鏈表。

在編譯器的 “加持下”,defer 語(yǔ)句會(huì)先調(diào)用 deferporc 函數(shù),new 一個(gè) _defer 結(jié)構(gòu)體,掛到 G 上。當(dāng)然,調(diào)用 new 之前會(huì)優(yōu)先從當(dāng)前 G 所綁定的 P 的 defer pool 里取,沒(méi)取到則會(huì)去全局的 defer pool 里取,實(shí)在沒(méi)有的話(huà)才新建一個(gè)。這是 Go runtime 里非常常見(jiàn)的操作,即設(shè)置多級(jí)緩存,提升運(yùn)行效率。

在執(zhí)行 RET 指令之前 (注意不是 return 之前),調(diào)用 deferreturn 函數(shù)完成 _defer 鏈表的遍歷,執(zhí)行完這條鏈上所有被 defered 的函數(shù) (如關(guān)閉文件、釋放連接、釋放鎖資源等)。在 deferreturn 函數(shù)的最后,會(huì)使用 jmpdefer 跳轉(zhuǎn)到之前被 defered 的函數(shù),這時(shí)控制權(quán)從 runtime 轉(zhuǎn)移到了用戶(hù)自定義的函數(shù)。這只是執(zhí)行了一個(gè)被 defered 的函數(shù),那這條鏈上其他的被 defered 的 函數(shù),該如何得到執(zhí)行?

答案就是控制權(quán)會(huì)再次交給 runtime,并再次執(zhí)行 deferreturn 函數(shù),完成 defer 鏈表的遍歷。

8 為什么無(wú)法從父 goroutine 恢復(fù)子 goroutine 的 panic

對(duì)于這個(gè)問(wèn)題,其實(shí)更普遍的問(wèn)題是:為什么無(wú)法 recover 其他 goroutine 里產(chǎn)生的 panic?

可能會(huì)好奇為什么會(huì)有人希望從父 goroutine 中恢復(fù)子 goroutine 內(nèi)產(chǎn)生的 panic。這是因?yàn)?,如果以下的情況發(fā)生在應(yīng)用程序內(nèi),那么整個(gè)進(jìn)程必然退出:

func() {
    panic("die die die")
}()

當(dāng)然,上面的代碼是顯式的 panic,實(shí)際情況下,如果不注意編碼規(guī)范,極有可能觸發(fā)一些本可以避免的恐慌錯(cuò)誤,例如訪問(wèn)越界:

func() {
    a :=  make([]int, 1)
    println(a[1])
}()

發(fā)生這種恐慌錯(cuò)誤對(duì)于服務(wù)端開(kāi)發(fā)而言幾乎是致命的,因?yàn)殚_(kāi)發(fā)者將無(wú)法預(yù)測(cè)服務(wù)的可用性,只能在錯(cuò)誤發(fā)生時(shí)發(fā)現(xiàn)該錯(cuò)誤,但這時(shí)服務(wù)不可用的損失已經(jīng)產(chǎn)生了。

那么,為什么不能從父 goroutine 中恢復(fù)子 goroutine 的 panic? 或者一般地說(shuō),為什么某個(gè) goroutine 不能捕獲其他 goroutine 內(nèi)產(chǎn)生的 panic?

其實(shí)這個(gè)問(wèn)題從 Go 誕生以來(lái)就一直被長(zhǎng)久地討論,而答案可以簡(jiǎn)單地認(rèn)為是設(shè)計(jì)使然:因?yàn)?goroutine 被設(shè)計(jì)為一個(gè)獨(dú)立的代碼執(zhí)行單元,擁有自己的執(zhí)行棧,不與其他 goroutine 共享任何數(shù)據(jù)。這意味著,無(wú)法讓 goroutine 擁有返回值、也無(wú)法讓 goroutine 擁有自身的 ID 編號(hào)等。若需要與其他 goroutine 產(chǎn)生交互,要么可以使用 channel 的方式與其他 goroutine 進(jìn)行通信,要么通過(guò)共享內(nèi)存同步方式對(duì)共享的內(nèi)存添加讀寫(xiě)鎖。

那一點(diǎn)辦法也沒(méi)有了嗎?方法自然有,但并不是完美的方法,這里提供一種思路。例如,如果希望有一個(gè)全局的恐慌捕獲中心,那么可以通過(guò)創(chuàng)建一個(gè)恐慌通知 channel,并在產(chǎn)生恐慌時(shí),通過(guò) recover 字段將其恢復(fù),并將發(fā)生的錯(cuò)誤通過(guò) channel 通知給這個(gè)全局的恐慌通知器:

package  main

import (
    "fmt"
    "time"
)

var notifier chan  interface{}
func  startGlobalPanicCapturing() {
    notifier =  make(chan  interface{})
    go  func() {
        for {
            select {
                case r :=  <- notifier:
                    fmt.Println(r)
            }
        }
    }()
}

func  main() {
    startGlobalPanicCapturing()
    // 產(chǎn)生恐慌,但該恐慌會(huì)被捕獲
    Go(func() {
        a :=  make([]int, 1)
        println(a[1])
    })
}

// Go 是一個(gè)恐慌安全的 goroutine
func  Go(f  func()) {
    go  func() {
        defer  func() {
            if r :=  recover(); r !=  nil {
                notifer <- i
            }
        }()
    }()
}

上面的 func Go(f func()) 本質(zhì)上是對(duì) go 關(guān)鍵字進(jìn)行了一層封裝,確保在執(zhí)行并發(fā)單元前插入一個(gè) defer,從而能夠保證恢復(fù)一些可恢復(fù)的錯(cuò)誤。

之所以說(shuō)這個(gè)方案并不完美,原因是如果函數(shù) f 內(nèi)部不再使用 Go 函數(shù)來(lái)創(chuàng)建 goroutine,而且含有繼續(xù)產(chǎn)生必然恐慌的代碼,那么仍然會(huì)出現(xiàn)不可恢復(fù)的情況。

func() {panic("die die die")}()

有人可能也許會(huì)想到,強(qiáng)制某個(gè)項(xiàng)目?jī)?nèi)均使用 Go 函數(shù)不就好了?事情也并沒(méi)有這么簡(jiǎn)單。因?yàn)槌丝苫謴?fù)的錯(cuò)誤外,還有一些不可恢復(fù)的運(yùn)行時(shí)恐慌 (例如并發(fā)讀寫(xiě) map),如果這類(lèi)恐慌一旦發(fā)生,那么任何補(bǔ)救都是徒勞的。解決這類(lèi)問(wèn)題的根本途徑是提高程序員自身對(duì)語(yǔ)言的認(rèn)識(shí),多進(jìn)行代碼測(cè)試,以及多通過(guò)運(yùn)維技術(shù)來(lái)增強(qiáng)容災(zāi)機(jī)制。

到此這篇關(guān)于深入學(xué)習(xí)Go延遲語(yǔ)句的文章就介紹到這了,更多相關(guān)Go延遲語(yǔ)句內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • GoFrame基于性能測(cè)試得知grpool使用場(chǎng)景

    GoFrame基于性能測(cè)試得知grpool使用場(chǎng)景

    這篇文章主要為大家介紹了GoFrame基于性能測(cè)試得知grpool使用場(chǎng)景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • Go中數(shù)組傳參的幾種方式小結(jié)

    Go中數(shù)組傳參的幾種方式小結(jié)

    本文主要介紹了Go中數(shù)組傳參的幾種方式小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • Go語(yǔ)言排序算法之插入排序與生成隨機(jī)數(shù)詳解

    Go語(yǔ)言排序算法之插入排序與生成隨機(jī)數(shù)詳解

    從這篇文章開(kāi)始將帶領(lǐng)大家學(xué)習(xí)Go語(yǔ)言的經(jīng)典排序算法,比如插入排序、選擇排序、冒泡排序、希爾排序、歸并排序、堆排序和快排,二分搜索,外部排序和MapReduce等,本文將先詳細(xì)介紹插入排序,并給大家分享了go語(yǔ)言生成隨機(jī)數(shù)的方法,下面來(lái)一起看看吧。
    2017-11-11
  • go語(yǔ)言處理TCP拆包/粘包的具體實(shí)現(xiàn)

    go語(yǔ)言處理TCP拆包/粘包的具體實(shí)現(xiàn)

    TCP的拆包/粘包也算是網(wǎng)絡(luò)編程中一個(gè)比較基礎(chǔ)的問(wèn)題了,本文主要介紹了go語(yǔ)言處理TCP拆包/粘包,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • 通過(guò)Golang實(shí)現(xiàn)無(wú)頭瀏覽器截圖

    通過(guò)Golang實(shí)現(xiàn)無(wú)頭瀏覽器截圖

    在Web開(kāi)發(fā)中,有時(shí)需要對(duì)網(wǎng)頁(yè)進(jìn)行截圖,以便進(jìn)行頁(yè)面預(yù)覽、測(cè)試等操作,本文為大家整理了Golang實(shí)現(xiàn)無(wú)頭瀏覽器的截圖的方法,感興趣的可以了解一下
    2023-05-05
  • Go語(yǔ)言實(shí)現(xiàn)廣播式并發(fā)聊天服務(wù)器

    Go語(yǔ)言實(shí)現(xiàn)廣播式并發(fā)聊天服務(wù)器

    本文主要介紹了Go語(yǔ)言實(shí)現(xiàn)廣播式并發(fā)聊天服務(wù)器,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08
  • golang中連接mysql數(shù)據(jù)庫(kù)

    golang中連接mysql數(shù)據(jù)庫(kù)

    這篇文章主要介紹了golang中連接mysql數(shù)據(jù)庫(kù)的步驟,幫助大家更好的理解和學(xué)習(xí)go語(yǔ)言,感興趣的朋友可以了解下
    2020-12-12
  • Go編程庫(kù)Sync.Pool用法示例詳解

    Go編程庫(kù)Sync.Pool用法示例詳解

    這篇文章主要為大家介紹了Go編程庫(kù)Sync.Pool用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • golang bufio包中Write方法的深入講解

    golang bufio包中Write方法的深入講解

    這篇文章主要給大家介紹了關(guān)于golang bufio包中Write方法的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-02-02
  • 詳解Golang實(shí)現(xiàn)http重定向https的方式

    詳解Golang實(shí)現(xiàn)http重定向https的方式

    這篇文章主要介紹了詳解Golang實(shí)現(xiàn)http重定向https的方式,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08

最新評(píng)論