<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』Go sync.Once 的妙用

          共 6087字,需瀏覽 13分鐘

           ·

          2021-09-07 11:58

          如果你曾用過 Go 中的 goroutines,你也許會遇到幾個并發(fā)原語,如 sync.Mutex, sync.WaitGroup 或是 sync.Map,但是你聽說過 sync.Once 么?

          也許你聽說過,那 go 文檔是怎么描述它的呢?

          Once 是只執(zhí)行一個操作的對象。

          聽起來很簡單,它有什么用處呢?

          由于某些原因,sync.Once 的用法并沒有很好的文檔記錄。在第一個.Do中的操作執(zhí)行完成前,將一直處于等待狀態(tài),這使得在執(zhí)行較昂貴的操作(通常緩存在 map 中)時非常有用。

          原生緩存方式

          假設(shè)你有一個熱門的網(wǎng)站,但它的后端 API 訪問不是很快,因此你決定將 API 結(jié)果通過 map 緩存在內(nèi)存中。以下是一個基本的解決方案:

          package main

          type QueryClient struct {
              cache map[string][]byte
              mutex *sync.Mutex
          }

          func (c *QueryClient) DoQuery(name string) []byte {
              // 檢查結(jié)果是否已緩存
              c.mutex.Lock()
              if cached, found := c.cache[name]; found {
                  c.mutex.Unlock()
                  return cached, nil
              }
              c.mutex.Unlock()

              // 如果未緩存則發(fā)出請求
              resp, err := http.Get("https://upstream.api/?query=" + url.QueryEscape(name))
              // 為簡潔起見,省略了錯誤處理和 resp.Body.Close
              result, err := ioutil.ReadAll(resp)

              // 將結(jié)果存儲在緩存中
              c.mutex.Lock()
              c.cache[name] = result
              c.mutex.Unlock()

              return result
          }

          看起來不錯,對吧?

          然而,如果有兩個 DoQuery 同時進(jìn)行調(diào)用會發(fā)生什么呢?競爭。兩方緩存都無法命中,并且都會向 upstream.api 執(zhí)行不必要的 HTTP 請求,而只有一個需要完成這個請求。

          不美觀但更好的緩存方式 我并沒有進(jìn)行統(tǒng)計(jì),但我認(rèn)為大家解決這個問題的另外一種方式是使用 channel、context 或 mutex。在這個例子中,可以將上文代碼調(diào)整為:

          package main

          type CacheEntry struct {
              data []byte
              wait <-chan struct{}
          }

          type QueryClient struct {
              cache map[string]*CacheEntry
              mutex *sync.Mutex
          }

          func (c *QueryClient) DoQuery(name string) []byte {
              // 檢查操作是否已啟動
              c.mutex.Lock()
              if cached, found := c.cache[name]; found {
                  c.mutex.Unlock()
                  // 等待完成
                  <-cached.wait
                  return cached.data, nil
              }

              entry := &CacheEntry{
                  data: result,
                  wait: make(chan struct{}),
              }
              c.cache[name] = entry
              c.mutex.Unlock()

              // 如果未緩存,則發(fā)出請求
              resp, err := http.Get("https://upstream.api/?query=" + url.QueryEscape(name))
              // 為簡潔起見,省略了錯誤處理和 resp.Body.Close
              entry.data, err = ioutil.ReadAll(resp)

              // 關(guān)閉 channel,傳遞操作完成信號
              // 立即返回
              close(entry.wait)

              return entry.data
          }

          這種方案不錯,但代碼的可讀性受到了很大影響。cached.wait 進(jìn)行了哪些操作不是很清晰,在不同情況下的操作流也并不直觀。

          使用 sync.Once

          我們來嘗試一下使用 sync.Once 方案:

          package main

          type CacheEntry struct {
              data []byte
              once *sync.Once
          }

          type QueryClient struct {
              cache map[string]*CacheEntry
              mutex *sync.Mutex
          }

          func (c *QueryClient) DoQuery(name string) []byte {
              c.mutex.Lock()
              entry, found := c.cache[name]
              if !found {
                  // 如果在緩存中未找到,創(chuàng)建新的 entry
                  entry = &CacheEntry{
                      once: new(sync.Once),
                  }
                  c.cache[name] = entry
              }
              c.mutex.Unlock()

              // 現(xiàn)在,當(dāng)我們調(diào)用 .Do 時,如果有一個正在同步進(jìn)行的操作
              // 它將一直阻塞,直到完成(并填充 entry.data)
              // 或者如果操作之前已經(jīng)完成過一次
              // 本次調(diào)用不會進(jìn)行操作,也不會阻塞
              entry.once.Do(func() {
                  resp, err := http.Get("https://upstream.api/?query=" + url.QueryEscape(name))
                  // 為簡潔起見,省略了錯誤處理和 resp.Body.Close
                  entry.data, err = ioutil.ReadAll(resp)
              })

              return entry.data
          }

          以上就是 sync.Once 的方案,和之前的示例很相似,但現(xiàn)在更容易理解(至少在我看來)。只有一個返回值,且代碼自上而下,非常直觀,而不必像之前一樣對 entry.wait channel 進(jìn)行閱讀和理解。

          進(jìn)一步閱讀/其他注意事項(xiàng) 另一個類似于 sync.Once 的機(jī)制是 golang.org/x/sync/singleflight。singleflight 只會刪除正在進(jìn)行中的請求中的重復(fù)請求(即不會持久化緩存),但與 sync.Once 相比,singleflight 通過 context 實(shí)現(xiàn)起來可能更簡潔(通過使用 selectctx.Done()),并且在生產(chǎn)環(huán)境中,可以通過 context 取消這一點(diǎn)很重要。singleflight 實(shí)現(xiàn)的模式和 sync.Once 十分接近,但如果 map 中存有值,則會提前返回。

          ianlancetaylor 建議結(jié)合 context 使用 sync.Once,方式如下:

          c := make(chan bool, 1)
          go func() {
              once.Do(f)
              c <- true
          }()
          select {
          case <-c:
          case <-ctxt.Done():
              return
          }

          ● 原文地址:https://blog.chuie.io/posts/synconce/


           原文作者:Jason Chu


          本文永久鏈接:https://github.com/gocn/translator/blob/master/2021/w34_the_underutilized_usefulness_of_sync_Once.md


          譯者:張宇


          校對:Cluas

          想要了解關(guān)于 Go 的更多資訊,還可以通過掃描的方式,進(jìn)群一起探討哦~



          瀏覽 49
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  午夜无码鲁丝片午夜精品一区二区 | 不卡不卡不卡不卡不卡国产精品视频 | 一级A级黄色片 | 一区二区福利视频 | 日韩三级影音先锋 |