<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go 有哪幾種無法恢復的致命場景?

          共 3109字,需瀏覽 7分鐘

           ·

          2022-01-03 23:43

          有一次事故現場,在緊急恢復后,他正在排查代碼,查了好一會。我回頭一看,這錯誤提醒很明顯就是致命錯誤,較好定位。

          但此時,他竟然在查 panic-recover 是不是哪里漏了,我表示大受震驚...

          今天就由煎魚給大家分享一下錯誤類型有哪幾種,又在什么場景下會觸發(fā)。

          錯誤類型

          error

          第一種是 Go 中最標準的 error 錯誤,其真身是一個 interface{}。

          如下:

          type?error?interface?{
          ????Error()?string
          }

          在日常工程中,我們只需要創(chuàng)建任意結構體,實現了 Error 方法,就可以認為是 error 錯誤類型。

          如下:

          type?errorString?struct?{
          ????s?string
          }

          func?(e?*errorString)?Error()?string?{
          ????return?e.s
          }

          在外部調用標準庫 API,一般如下:

          f,?err?:=?os.Open("filename.ext")
          if?err?!=?nil?{
          ????log.Fatal(err)
          }
          //?do?something?with?the?open?*File?f

          我們會約定最后一個參數為 error 類型,一般常見于第二個參數,可以有個約定俗成的習慣。

          panic

          第二種是 Go 中的異常處理 panic,能夠產生異常錯誤,結合 panic+recover 可以扭轉程序的運行狀態(tài)。

          如下:

          package?main

          import?"os"

          func?main()?{
          ????panic("a?problem")

          ????_,?err?:=?os.Create("/tmp/file")
          ????if?err?!=?nil?{
          ????????panic(err)
          ????}
          }

          輸出結果:

          $?go?run?panic.go
          panic:?a?problem
          goroutine?1?[running]:
          main.main()
          ????/.../panic.go:12?+0x47
          ...
          exit?status?2

          如果沒有使用 recover 作為捕獲,就會導致程序中斷。也因此經常被人誤以為程序中斷,就 100% 是 panic 導致的。

          這是一個誤區(qū)。

          throw

          第三種是 Go 初學者經常踩坑,也不知道的錯誤類型,那就是致命錯誤 throw。

          這個錯誤類型,在用戶側是沒法主動調用的,均為 Go 底層自行調用的,像是大家常見的 map 并發(fā)讀寫,就是由此觸發(fā)。

          其源碼如下:

          func?throw(s?string)?{
          ?systemstack(func()?{
          ??print("fatal?error:?",?s,?"\n")
          ?})
          ?gp?:=?getg()
          ?if?gp.m.throwing?==?0?{
          ??gp.m.throwing?=?1
          ?}
          ?fatalthrow()
          ?*(*int)(nil)?=?0?//?not?reached
          }

          根據上述程序,會獲取當前 G 的實例,并設置其 M 的 throwing 狀態(tài)為 1。

          狀態(tài)設置好后,會調用 fatalthrow 方法進行真正的 crash 相關操作:

          func?fatalthrow()?{
          ?pc?:=?getcallerpc()
          ?sp?:=?getcallersp()
          ?gp?:=?getg()
          ?
          ?systemstack(func()?{
          ??startpanic_m()
          ??if?dopanic_m(gp,?pc,?sp)?{
          ???crash()
          ??}

          ??exit(2)
          ?})

          ?*(*int)(nil)?=?0?//?not?reached
          }

          主體邏輯是發(fā)送 _SIGABRT 信號量,最后調用 exit 方法退出,所以你會發(fā)現這是攔也攔不住的 “致命” 錯誤。

          致命場景

          為此,作為一名 “成熟” 的 Go 工程師,除了保障自己程序的健壯性外,我也在網上收集了一些致命的錯誤場景,分享給大家。

          一起學習和規(guī)避這些致命場景,年底爭取拿個 A,不要背上 P0 事故。

          并發(fā)讀寫 map

          func?foo()?{
          ?m?:=?map[string]int{}
          ?go?func()?{
          ??for?{
          ???m["煎魚1"]?=?1
          ??}
          ?}()
          ?for?{
          ??_?=?m["煎魚2"]
          ?}
          }

          輸出結果:

          fatal?error:?concurrent?map?read?and?map?write

          goroutine?1?[running]:
          runtime.throw(0x1078103,?0x21)
          ...

          堆棧內存耗盡

          func?foo()?{
          ?var?f?func(a?[1000]int64)
          ?f?=?func(a?[1000]int64)?{
          ??f(a)
          ?}
          ?f([1000]int64{})
          }

          輸出結果:

          runtime:?goroutine?stack?exceeds?1000000000-byte?limit
          runtime:?sp=0xc0200e1bf0?stack=[0xc0200e0000,?0xc0400e0000]
          fatal?error:?stack?overflow

          runtime?stack:
          runtime.throw(0x1074ba3,?0xe)
          ????????/usr/local/Cellar/go/1.16.6/libexec/src/runtime/panic.go:1117?+0x72
          runtime.newstack()
          ...

          將 nil 函數作為 goroutine 啟動

          func?foo()?{
          ?var?f?func()
          ?go?f()
          }

          輸出結果:

          fatal?error:?go?of?nil?func?value

          goroutine?1?[running]:
          main.foo()
          ...

          goroutines 死鎖

          func?foo()?{
          ?select?{}
          }

          輸出結果:

          fatal?error:?all?goroutines?are?asleep?-?deadlock!

          goroutine?1?[select?(no?cases)]:
          main.foo()
          ...

          線程限制耗盡

          如果你的 goroutines 被 IO 操作阻塞了,新的線程可能會被啟動來執(zhí)行你的其他 goroutines。

          Go 的最大的線程數是有默認限制的,如果達到了這個限制,你的應用程序就會崩潰。

          會出現如下輸出結果:

          fatal?error:?thread?exhaustion
          ...

          可以通過調用 runtime.SetMaxThreads 方法增大線程數,不過也需要考量是否程序有問題。

          超出可用內存

          如果你執(zhí)行的操作,例如:下載大文件等。導致應用程序占用內存過大,程序上漲,導致 OOM。

          會出現如下輸出結果:

          fatal?error:?runtime:?out?of?memory
          ...

          建議處理掉一些程序,或者換新電腦了。

          總結

          在今天這篇文章中,我們介紹了 Go 語言的三種錯誤類型。其中針對大家最少見,但一碰到就很容易翻車的致命錯誤 fatal error 進行了介紹,給出了一些經典案例。

          希望大家后續(xù)能夠規(guī)避,你有沒有遇到過其中的場景

          歡迎在評論區(qū)交流和留言:)

          參考

          • Are all runtime errors recoverable in Go?


          瀏覽 43
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品久久久久久久毛片明星 | 伊人久久香蕉网 | 日韩黄在线 | 欧美成人精品一区二区特级毛片 | 国内精品毛片 |