最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go并發(fā)原語(yǔ)之SingleFlight請(qǐng)求合并方法實(shí)例

 更新時(shí)間:2023年12月14日 10:42:48   作者:fliyu  
本文我們來(lái)學(xué)習(xí)一下 Go 語(yǔ)言的擴(kuò)展并發(fā)原語(yǔ):SingleFlight,SingleFlight 的作用是將并發(fā)請(qǐng)求合并成一個(gè)請(qǐng)求,以減少重復(fù)的進(jìn)程來(lái)優(yōu)化 Go 代碼

SingleFlight 的使用場(chǎng)景

在處理多個(gè) goroutine 同時(shí)調(diào)用同一個(gè)函數(shù)的時(shí)候,如何只用一個(gè) goroutine 去調(diào)用一次函數(shù),并將返回結(jié)果給到所有 goroutine,這是可以使用 SingleFlight,可以減少并發(fā)調(diào)用的數(shù)量。

在高并發(fā)請(qǐng)求場(chǎng)景中,例如秒殺場(chǎng)景:多個(gè)用戶在同一時(shí)間查詢庫(kù)存數(shù),這時(shí)候?qū)τ谒械挠脩舳?,同一時(shí)間查詢結(jié)果都是一樣的,如果后臺(tái)都去查緩存或者數(shù)據(jù)庫(kù),那么性能壓力很大。如果相同時(shí)間只有一個(gè)查詢,那么性能將顯著提升。

一句話總結(jié):SingleFlight 主要作用是合并并發(fā)請(qǐng)求的場(chǎng)景,針對(duì)于相同的讀請(qǐng)求。

SingleFlight 的基本使用

下面先看看這段代碼,5個(gè)協(xié)程同時(shí)并發(fā)返回 getProductById ,看看輸出結(jié)果如何:

func main() {
  var wg sync.WaitGroup
  for i := 0; i < 5; i++ {
    wg.Add(1)
    go func() {
      defer wg.Done()
      result := getProductById("商品A")
      fmt.Printf("%v\n", result)
    }()
  }
  wg.Wait()
}
func getProductById(name string) string {
  fmt.Println("getProductById doing...")
  time.Sleep(time.Millisecond * 10) // 模擬一下耗時(shí)
  return name
}
$ go run main.go
getProductById doing...
getProductById doing...
getProductById doing...
getProductById doing...
getProductById doing...
商品A
商品A
商品A
商品A
商品A

可以看出 getProductById 方法被訪問了五次,那么如何通過 SingleFlight 進(jìn)行優(yōu)化呢?

定義一個(gè)全局變量 SingleFlight,在訪問 getProductById 方法時(shí)調(diào)用 Do 方法,即可實(shí)現(xiàn)同一時(shí)間只有一次方法,代碼如下:

import (
  "fmt"
  "golang.org/x/sync/singleflight"
  "sync"
  "time"
)
var g singleflight.Group
func main() {
  var wg sync.WaitGroup
  for i := 0; i < 5; i++ {
    wg.Add(1)
    go func() {
      defer wg.Done()
      resp, _, _ := g.Do("商品A", func() (interface{}, error) {
        result := getProductById("商品A")
        return result, nil
      })
      fmt.Printf("%v\n", resp)
    }()
  }
  wg.Wait()
}
func getProductById(name string) string {
  fmt.Println("getProductById doing...")
  time.Sleep(time.Millisecond * 10) // 模擬一下耗時(shí)
  return name
}
$ go run main.go
getProductById doing...
商品A
商品A
商品A
商品A
商品A

你可能會(huì)想 SingleFlight 和 sync.Once 的區(qū)別,sync.Once 主要是用在單次初始化場(chǎng)景中,而 SingleFlight 主要用在合并請(qǐng)求中,針對(duì)于同一時(shí)間的并發(fā)場(chǎng)景。

SingleFlight 的實(shí)現(xiàn)原理

SingleFlight 的數(shù)據(jù)結(jié)構(gòu)是 Group ,結(jié)構(gòu)如下:

// call is an in-flight or completed singleflight.Do call
type call struct {
  wg sync.WaitGroup
  // These fields are written once before the WaitGroup is done
  // and are only read after the WaitGroup is done.
  val interface{}
  err error
  // These fields are read and written with the singleflight
  // mutex held before the WaitGroup is done, and are read but
  // not written after the WaitGroup is done.
  dups  int
  chans []chan<- Result
}
// Group represents a class of work and forms a namespace in
// which units of work can be executed with duplicate suppression.
type Group struct {
  mu sync.Mutex       // protects m
  m  map[string]*call // lazily initialized
}
// Result holds the results of Do, so they can be passed
// on a channel.
type Result struct {
  Val    interface{}
  Err    error
  Shared bool
}

可以看出,SingleFlight 是使用互斥鎖 Mutex 和 Map 來(lái)實(shí)現(xiàn)的?;コ怄i Mutex 提供并發(fā)時(shí)的讀寫保護(hù),而 Map 用于保存同一個(gè) key 正在處理的請(qǐng)求。

其提供了3個(gè)方法:

Do 方法的實(shí)現(xiàn)邏輯

// Do executes and returns the results of the given function, making
// sure that only one execution is in-flight for a given key at a
// time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
// The return value shared indicates whether v was given to multiple callers.
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
  g.mu.Lock()
  if g.m == nil {
    g.m = make(map[string]*call)
  }
  if c, ok := g.m[key]; ok {
    c.dups++
    g.mu.Unlock()
    c.wg.Wait()
    if e, ok := c.err.(*panicError); ok {
      panic(e)
    } else if c.err == errGoexit {
      runtime.Goexit()
    }
    return c.val, c.err, true
  }
  c := new(call)
  c.wg.Add(1)
  g.m[key] = c
  g.mu.Unlock()
  g.doCall(c, key, fn)
  return c.val, c.err, c.dups > 0
}

SingleFlight 定義了一個(gè)輔助對(duì)象 call,用于代表正在執(zhí)行 fn 函數(shù)的請(qǐng)求或者是否已經(jīng)執(zhí)行完請(qǐng)求。

  • 如果存在相同的 key,其他請(qǐng)求將會(huì)等待這個(gè) key 執(zhí)行完成,并使用第一個(gè) key 獲取到的請(qǐng)求結(jié)果
  • 如果不存在,創(chuàng)建一個(gè) call ,并將其加入到 map 中,執(zhí)行調(diào)用 fn 函數(shù)。

DoChan 方法的實(shí)現(xiàn)邏輯

而 DoChan 方法與 Do 方法類似:

// DoChan is like Do but returns a channel that will receive the
// results when they are ready.
//
// The returned channel will not be closed.
func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result {
  ch := make(chan Result, 1)
  g.mu.Lock()
  if g.m == nil {
    g.m = make(map[string]*call)
  }
  if c, ok := g.m[key]; ok {
    c.dups++
    c.chans = append(c.chans, ch)
    g.mu.Unlock()
    return ch
  }
  c := &call{chans: []chan<- Result{ch}}
  c.wg.Add(1)
  g.m[key] = c
  g.mu.Unlock()
  go g.doCall(c, key, fn)
  return ch
}

Forget 方法的實(shí)現(xiàn)邏輯

// Forget tells the singleflight to forget about a key.  Future calls
// to Do for this key will call the function rather than waiting for
// an earlier call to complete.
func (g *Group) Forget(key string) {
  g.mu.Lock()
  delete(g.m, key)
  g.mu.Unlock()
}

將 key 從 map 中刪除。

總結(jié)

使用 SingleFlight 時(shí),通過將多個(gè)請(qǐng)求合并成一個(gè),降低并發(fā)訪問的壓力,極大地提升了系統(tǒng)性能,針對(duì)于多并發(fā)讀請(qǐng)求的場(chǎng)景,可以考慮是否滿足 SingleFlight 的使用情況。

而對(duì)于并發(fā)寫請(qǐng)求的場(chǎng)景,如果是多次寫只需要一次的情況,那么也是滿足的。例如:每個(gè) http 請(qǐng)求都會(huì)攜帶 token,每次請(qǐng)求都需要把 token 存入緩存或者寫入數(shù)據(jù)庫(kù),如果多次并發(fā)請(qǐng)求同時(shí)來(lái),只需要寫一次即可

以上就是Go并發(fā)原語(yǔ)之SingleFlight請(qǐng)求合并方法實(shí)例的詳細(xì)內(nèi)容,更多關(guān)于Go SingleFlight 請(qǐng)求合并的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語(yǔ)言Zap日志庫(kù)使用教程

    Go語(yǔ)言Zap日志庫(kù)使用教程

    在項(xiàng)目開發(fā)中,經(jīng)常需要把程序運(yùn)行過程中各種信息記錄下來(lái),有了詳細(xì)的日志有助于問題排查和功能優(yōu)化;但如何選擇和使用性能好功能強(qiáng)大的日志庫(kù),這個(gè)就需要我們從多角度考慮
    2023-02-02
  • Go?連接?MySQL之?MySQL?預(yù)處理詳解

    Go?連接?MySQL之?MySQL?預(yù)處理詳解

    Go語(yǔ)言提供了豐富的庫(kù)和工具,可以方便地連接MySQL數(shù)據(jù)庫(kù)。MySQL預(yù)處理是一種提高數(shù)據(jù)庫(kù)操作效率和安全性的技術(shù)。Go語(yǔ)言中的第三方庫(kù)提供了MySQL預(yù)處理的支持,通過使用預(yù)處理語(yǔ)句,可以避免SQL注入攻擊,并且可以提高數(shù)據(jù)庫(kù)操作的效率。
    2023-06-06
  • Windows下升級(jí)go版本過程詳解

    Windows下升級(jí)go版本過程詳解

    這篇文章主要為大家介紹了Windows下升級(jí)go版本過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • 基于Go語(yǔ)言實(shí)現(xiàn)壓縮文件處理

    基于Go語(yǔ)言實(shí)現(xiàn)壓縮文件處理

    在現(xiàn)代的應(yīng)用開發(fā)中,處理壓縮文件(如 .zip 格式)是常見的需求,本文將介紹如何使用 Go 語(yǔ)言封裝一個(gè) ziputil 包,來(lái)處理文件的壓縮和解壓操作,需要的可以了解下
    2024-11-11
  • 如何在Golang中運(yùn)行JavaScript

    如何在Golang中運(yùn)行JavaScript

    最近寫一個(gè)程序,接口返回的數(shù)據(jù)是js格式的,需要通過golang來(lái)解析js,所以下面這篇文章主要給大家介紹了關(guān)于如何在Golang中運(yùn)行JavaScript的相關(guān)資料,需要的朋友可以參考下
    2022-01-01
  • 使用golang實(shí)現(xiàn)PDF圖片提取

    使用golang實(shí)現(xiàn)PDF圖片提取

    這篇文章主要為大家詳細(xì)介紹了如何使用golang實(shí)現(xiàn)PDF圖片提取功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-11-11
  • Golang的select多路復(fù)用及channel使用操作

    Golang的select多路復(fù)用及channel使用操作

    這篇文章主要介紹了Golang的select多路復(fù)用及channel使用操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧
    2020-12-12
  • Go語(yǔ)言TCP從原理到代碼實(shí)現(xiàn)詳解

    Go語(yǔ)言TCP從原理到代碼實(shí)現(xiàn)詳解

    這篇文章主要為大家介紹了Go語(yǔ)言TCP從原理到代碼實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Golang的strings.Split()踩坑記錄

    Golang的strings.Split()踩坑記錄

    工作中,當(dāng)我們需要對(duì)字符串按照某個(gè)字符串切分成字符串?dāng)?shù)組數(shù)時(shí),常用到strings.Split(),本文主要介紹了Golang的strings.Split()踩坑記錄,感興趣的可以了解一下
    2022-05-05
  • 深入解析Go語(yǔ)言中crypto/subtle加密庫(kù)

    深入解析Go語(yǔ)言中crypto/subtle加密庫(kù)

    本文主要介紹了深入解析Go語(yǔ)言中crypto/subtle加密庫(kù),詳細(xì)介紹crypto/subtle加密庫(kù)主要函數(shù)的用途、工作原理及實(shí)際應(yīng)用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-02-02

最新評(píng)論

白水县| 利川市| 舞阳县| 西和县| 桓仁| 海丰县| 成安县| 郎溪县| 大城县| 商城县| 华宁县| 阜新| 泰兴市| 新源县| 大同县| 漠河县| 合江县| 姚安县| 定兴县| 定西市| 东兴市| 正镶白旗| 临朐县| 建昌县| 平塘县| 常德市| 长海县| 筠连县| 兴安盟| 朝阳区| 凤翔县| 沂源县| 东乡族自治县| 临海市| 莎车县| 万山特区| 龙游县| 南阳市| 阜城县| 钦州市| 乡城县|