乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      在Go中,你犯過這些錯(cuò)誤嗎

       風(fēng)聲之家 2021-04-28

      Go語言中文網(wǎng) 今天

      以下文章來源于吳親強(qiáng)的深夜食堂 ,作者吳親庫里

      吳親強(qiáng)的深夜食堂

      吳親強(qiáng)的深夜食堂

      關(guān)注一些奇奇怪怪的設(shè)計(jì),分享一些有有趣趣的生活

      迭代器變量上使用 goroutine

      這算高頻吧。

      package main

      import (
        "fmt"
        "sync"
      )

      func main() {
        var wg sync.WaitGroup
        items := []int{12345}
        for index, _ := range items {
          wg.Add(1)
          go func() {
            defer wg.Done()
            fmt.Printf("item:%v\\n", items[index])
          }()
        }
        wg.Wait()
      }

      一個(gè)很簡單的利用 sync.waitGroup 做任務(wù)編排的場景,看一下好像沒啥問題,運(yùn)行看看結(jié)果。

      圖片

      為啥不是1-5(當(dāng)然不是順序的)。

      原因很簡單,循環(huán)器中的 i 實(shí)際上是一個(gè)單變量,go func 里的閉包只綁定在一個(gè)變量上, 每個(gè) goroutine 可能要等到循環(huán)結(jié)束才真正的運(yùn)行,這時(shí)候運(yùn)行的 i 值大概率就是5了。沒人能保證這個(gè)過程,有的只是手段。

      正確的做法,

      func main() {
        var wg sync.WaitGroup

        items := []int{12345}
        for index, _ := range items {
          wg.Add(1)
          go func(i int) {
            defer wg.Done()
            fmt.Printf("item:%v\\n", items[i])
          }(index)
        }
        wg.Wait()
      }

      通過將 i 作為一個(gè)參數(shù)傳入閉包中,i 每次迭代都會(huì)被求值, 并放置在 goroutine 的堆棧中,因此每個(gè)切片元素最終都會(huì)被執(zhí)行打印。

      或者這樣,

      for index, _ := range items {
          wg.Add(1)
          i:=index
          go func() {
            defer wg.Done()
            fmt.Printf("item:%v\\n", items[i])
          }()
        }


      WaitGroup

      上面的例子有用到 sync.waitGroup,使用不當(dāng),也會(huì)犯錯(cuò)。

      我把上面的例子稍微改動(dòng)復(fù)雜一點(diǎn)點(diǎn)。

      package main

      import (
        "errors"
        "github.com/prometheus/common/log"
        "sync"
      )

      type User struct {
        userId int
      }

      func main() {
        var userList []User
        for i := 0; i < 10; i++ {
          userList = append(userList, User{userId: i})
        }

        var wg sync.WaitGroup
        for i, _ := range userList {
          wg.Add(1)
          go func(item int) {
            _, err := Do(userList[item])
            if err != nil {
              log.Infof("err message:%v\\n", err)
              return
            }
            wg.Done()
          }(i)
        }
        wg.Wait()

        // 處理其他事務(wù)
      }

      func Do(user User) (string, error) {
        // 處理雜七雜八的業(yè)務(wù)....
        if user.userId == 9 {
          // 此人是非法用戶
          return "失敗", errors.New("非法用戶")
        }
        return "成功"nil
      }

      發(fā)現(xiàn)問題嚴(yán)重性了嗎?

      當(dāng)用戶id等于9的時(shí)候,err !=nil 直接 return 了,導(dǎo)致 waitGroup 計(jì)數(shù)器根本沒機(jī)會(huì)減1, 最終 wait 會(huì)阻塞,多么可怕的 bug

      在絕大多數(shù)的場景下,我們都必須這樣:

      func main() {
        var userList []User
        for i := 0; i < 10; i++ {
          userList = append(userList, User{userId: i})
        }
        var wg sync.WaitGroup
        for i, _ := range userList {
          wg.Add(1)
          go func(item int) {
            defer wg.Done() //重點(diǎn)

            //....業(yè)務(wù)代碼
            //....業(yè)務(wù)代碼
            _, err := Do(userList[item])
            if err != nil {
              log.Infof("err message:%v\n", err)
              return
            }
          }(i)
        }
        wg.Wait()
      }


      野生 goroutine

      我不知道你們公司是咋么處理異步操作的,是下面這樣嗎?

      func main() {
        // doSomething
        go func() {
          // doSomething
        }()
      }

      我們?yōu)榱朔乐钩绦蛑谐霈F(xiàn)不可預(yù)知的 panic,導(dǎo)致程序直接掛掉,都會(huì)加入 recover,

      func main() {
        defer func() {
          if err := recover(); err != nil {
            fmt.Printf("%v\n", err)
          }
        }()
        panic("處理失敗")
      }

      但是如果這時(shí)候我們直接開啟一個(gè) goroutine,在這個(gè) goroutine 里面發(fā)生了 panic,

      func main() {
        defer func() {
          if err := recover(); err != nil {
            fmt.Printf("%v\n", err)
          }
        }()
        go func() {
          panic("處理失敗")
        }()

        time.Sleep(2 * time.Second)
      }

      此時(shí)最外層的 recover 并不能捕獲,程序會(huì)直接掛掉 圖片

      但是你總不能每次開啟一個(gè)新的 goroutine 就在里面 recover,

      func main() {
        defer func() {
          if err := recover(); err != nil {
            fmt.Printf("%v\n", err)
          }
        }()

        // func1
        go func() {
          defer func() {
            if err := recover(); err != nil {
              fmt.Printf("%v\n", err)
            }
          }()
          panic("錯(cuò)誤失敗")
        }()

        // func2
        go func() {
          defer func() {
            if err := recover(); err != nil {
              fmt.Printf("%v\n", err)
            }
          }()
          panic("請求錯(cuò)誤")
        }()

        time.Sleep(2 * time.Second)
      }

      多蠢啊。所以基本上大家都會(huì)包一層。

      package main

      import (
        "fmt"
        "time"
      )

      func main() {
        defer func() {
          if err := recover(); err != nil {
            fmt.Printf("%v\n", err)
          }
        }()

        // func1
        Go(func() {
          panic("錯(cuò)誤失敗")
        })

        // func2
        Go(func() {
          panic("請求錯(cuò)誤")
        })

        time.Sleep(2 * time.Second)
      }

      func Go(fn func()) {
        go RunSafe(fn)
      }

      func RunSafe(fn func()) {
        defer func() {
          if err := recover(); err != nil {
            fmt.Printf("錯(cuò)誤:%v\n", err)
          }
        }()
        fn()
      }

      當(dāng)然我這里只是簡單都打印一些日志信息,一般還會(huì)帶上堆棧都信息。


      channel

      channel  go 中的地位實(shí)在太高了,各大開源項(xiàng)目到處都是 channel 的影子, 以至于你在工業(yè)級的項(xiàng)目 issues 中搜索 channel ,能看到很多的 bug, 比如 etcd 這個(gè) issue圖片

      一個(gè)往已關(guān)閉的 channel 中發(fā)送數(shù)據(jù)引發(fā)的 panic,等等類似場景很多。

      這個(gè)故事告訴我們,否管大不大佬,改寫的 bug 還是會(huì)寫,手動(dòng)狗頭

      channel 除了上述高頻出現(xiàn)的錯(cuò)誤,還有以下幾點(diǎn):


      直接關(guān)閉一個(gè) nil 值 channel 會(huì)引發(fā) panic

      package main

      func main() {
        var ch chan struct{}
        close(ch)
      }


      關(guān)閉一個(gè)已關(guān)閉的 channel 會(huì)引發(fā) panic。

      package main

      func main() {
        ch := make(chan struct{})
        close(ch)
        close(ch)
      }

      另外,有時(shí)候使用 channel 不小心會(huì)導(dǎo)致 goroutine 泄露,比如下面這種情況,

      package main

      import (
        "context"
        "fmt"
        "time"
      )

      func main() {
        ch := make(chan struct{})
        cx, _ := context.WithTimeout(context.Background(), time.Second)
        go func() {
          time.Sleep(2 * time.Second)
          ch <- struct{}{}
          fmt.Println("goroutine 結(jié)束")
        }()

        select {
        case <-ch:
          fmt.Println("res")
        case <-cx.Done():
          fmt.Println("timeout")
        }
        time.Sleep(5 * time.Second)
      }

      啟動(dòng)一個(gè) goroutine 去處理業(yè)務(wù),業(yè)務(wù)需要執(zhí)行2秒,而我們設(shè)置的超時(shí)時(shí)間是1秒。 這就會(huì)導(dǎo)致 channel 從未被讀取, 我們知道沒有緩沖的 channel 必須等發(fā)送方和接收方都準(zhǔn)備好才能操作。 此時(shí) goroutine 會(huì)被永久阻塞在 ch <- struct{}{} 這行代碼,除非程序結(jié)束。 而這就是 goroutine 泄露

      解決這個(gè)也很簡單,把無緩沖的 channel 改成緩沖為1。

      總結(jié)

      這篇文章主要介紹了使用 Go 在日常開發(fā)中容易犯下的錯(cuò)。 當(dāng)然還遠(yuǎn)遠(yuǎn)不止這些,你可以在下方留言中補(bǔ)充你犯過的錯(cuò)。

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多