国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

講完Go并發(fā)控制,講講并發(fā)抑制

開(kāi)發(fā) 前端
在高并發(fā)的狀態(tài)下,一般會(huì)給熱點(diǎn)數(shù)據(jù)設(shè)置緩存。但數(shù)據(jù)第一次訪問(wèn)或者緩存失效的狀態(tài)下,如果直接去查詢(xún)數(shù)據(jù)庫(kù),會(huì)給數(shù)據(jù)庫(kù)造成極大壓力,甚至直接打爆數(shù)據(jù)庫(kù)。

已知有一個(gè)函數(shù)search,能夠按照關(guān)鍵詞執(zhí)行搜索,coSearch能夠批量并發(fā)查詢(xún)。

讓我們把目光定位到search上,search通過(guò)查詢(xún)數(shù)據(jù)庫(kù)或者調(diào)用其他api來(lái)完成搜索,這是一個(gè)相對(duì)耗時(shí)和消耗資源的操作。

當(dāng)多個(gè)相同的關(guān)鍵詞并發(fā)查詢(xún)(調(diào)用search函數(shù))時(shí),我們希望只產(chǎn)生一次數(shù)據(jù)庫(kù)調(diào)用(調(diào)用query),第一個(gè)查詢(xún)未完成時(shí)后續(xù)的重復(fù)查詢(xún)會(huì)等待,當(dāng)?shù)谝粋€(gè)查詢(xún)完成時(shí)則會(huì)與其他查詢(xún)分享結(jié)果,這樣一來(lái)雖然只執(zhí)行了一次數(shù)據(jù)庫(kù)調(diào)用但是所有查詢(xún)都拿到了最終的結(jié)果。

圖片圖片

什么是并發(fā)抑制:

package main

import (
 "context"
 "fmt"
 "sync"
 "time"

 "golang.org/x/sync/errgroup"
)

func query(ctx context.Context, word string) (string, error) {
 fmt.Println("searching: ", word)
 time.Sleep(5 * time.Second)

 return fmt.Sprintf("result: %s", word), nil // 模擬結(jié)果
}

// 實(shí)現(xiàn)search,在重復(fù)并發(fā)調(diào)用下僅執(zhí)行一次query
// 其他并發(fā)共享這次query的結(jié)果
func search(ctx context.Context, word string) (string, error) {
    return query(ctx, word)
}

func coSearch(ctx context.Context, words []string) ([]string, error) {
 g, ctx := errgroup.WithContext(ctx)
 g.SetLimit(10)

 results := make([]string, len(words))

 for i, word := range words {
  i, word := i, word

  g.Go(func() error {
   result, err := search(ctx, word)
   if err != nil {
    return err
   }

   results[i] = result
   return nil
  })
 }

 err := g.Wait()

 return results, err
}

func main() {
 words := []string{"Go","Go", "Go", "Rust", "PHP", "JavaScript", "Java"}
 results, err := coSearch(context.Background(), words)
 if err != nil {
  fmt.Println(err)
  return
 }

 fmt.Println(results)
}

好了,可以先暫停想想該如何實(shí)現(xiàn)search函數(shù)了。

一步一步實(shí)現(xiàn)并發(fā)抑制

我們先假設(shè)所有查詢(xún)關(guān)鍵詞都一樣,那么問(wèn)題簡(jiǎn)化成并發(fā)執(zhí)行search時(shí),只在第一次search時(shí)調(diào)用query,其他的search并發(fā)調(diào)用等待并共享這次的查詢(xún)結(jié)果。

通過(guò)waiting變量,其他goroutine等待第一個(gè)goroutine數(shù)據(jù)庫(kù)調(diào)用完成,那么如何讓其他goroutine等待在這個(gè)位置呢?

func main() {
 words := []string{"Go", "Go", "Go", "Go", "Go"}
 results, err := coSearch(context.Background(), words)
 if err != nil {
  fmt.Println(err)
  return
 }

 fmt.Println(results)
}

var (
 waiting bool
 resp    string
 err     error
)

func search(ctx context.Context, word string) (string, error) {
  if waiting {
    // 等待resp, err被賦值,即第一個(gè)query完成后再返回
    // ...?
      return resp, err
  }

  waiting = true
  resp, err = query(ctx, word)
  waiting = false

  return resp, err
}

func query(ctx context.Context, word string) (string, error) {
 fmt.Println("searching: ", word)
 time.Sleep(5 * time.Second)

 return fmt.Sprintf("result: %s", word), nil // 模擬結(jié)果
}

sync.WaitGroup{}并發(fā)控制

sync.WaitGroup{}是并發(fā)控制的核心,這里再次重申下用法:

  • 當(dāng)新運(yùn)行一個(gè)goroutine時(shí),我們需要調(diào)用wg.Add(1)。
  • 當(dāng)一個(gè)goroutine運(yùn)行完成的時(shí)候,我們需要調(diào)用wg.Done()。
  • wg.Wait()讓程序阻塞在此處,直到所有的goroutine運(yùn)行完畢。

利用 sync.WaitGroup{}便可實(shí)現(xiàn)上文代碼中等待的效果:

var (
 wg      sync.WaitGroup
 waiting bool
 resp    string
 err     error
)

func search(ctx context.Context, word string) (string, error) {
 if waiting {
  // 其他goroutine等待第一個(gè)goroutine執(zhí)行完成
  wg.Wait()
  return resp, err
 }

 waiting = true
    
 wg.Add(1)
 resp, err = query(ctx, word)
    wg.Done() // 第一個(gè)goroutine執(zhí)行完成
    
 waiting = false

 return resp, err
}

并發(fā)安全

當(dāng)多個(gè)goroutine對(duì)同一個(gè)內(nèi)存區(qū)域進(jìn)行讀寫(xiě)時(shí),就會(huì)產(chǎn)生并發(fā)安全的問(wèn)題,它會(huì)導(dǎo)致程序運(yùn)行的結(jié)果不符合預(yù)期,而上文的程序并發(fā)的讀寫(xiě)了waiting變量,需要給waiting變量加把鎖。

釋放鎖的位置非常的有技巧,如果在在wg.Add(1)之前mu.Unlock(),可能 wg.Add(1)還未來(lái)得執(zhí)行其他goroutine已經(jīng)執(zhí)行了wg.Wait(),并獲取到了錯(cuò)誤的數(shù)據(jù)。

unlock在add之前;

var (
  wg      sync.WaitGroup
  mu      sync.Mutex
  waiting bool
  resp    string
  err     error
)

func search(ctx context.Context, word string) (string, error) {
 mu.Lock()

 if waiting {
  mu.Unlock()
  wg.Wait()
  return resp, err
 }

 waiting = true
    
 wg.Add(1)
    // 在wg.Add(1)之后釋放鎖,保證其他goroutine被wg.Wait()阻塞
 mu.Unlock()

 resp, err = query(ctx, word)
    wg.Done()

 mu.Lock()
 waiting = false
 mu.Unlock() 
    
 return resp, err
}

完整版本

現(xiàn)在可以針對(duì)不同的關(guān)鍵詞做區(qū)分了,使用一個(gè)map來(lái)代替原有的waiting,并將每一個(gè)關(guān)鍵詞查詢(xún)的WaitGroup和結(jié)果打包到map的value中。

type call struct {
 wg   sync.WaitGroup
 resp string
 err  error
}

var (
    mu sync.Mutex
    m = make(map[string]*call)
)

func search(ctx context.Context, word string) (string, error) {
 mu.Lock()

 if c, ok := m[word]; ok {
  mu.Unlock()
  c.wg.Wait()
  return c.resp, c.err
 }

 c := &call{}
 m[word] = c

 c.wg.Add(1)
 // 在wg.Add(1)之后才釋放鎖,保證其他goroutine被wg.Wait()阻塞
 mu.Unlock()

 c.resp, c.err = query(ctx, word)
 c.wg.Done()

 mu.Lock()
 delete(m, word)
 mu.Unlock()

 return c.resp, c.err
}

開(kāi)源庫(kù) golang.org/x/sync/singleflight

上面一步一步教大家手搓了一個(gè)并發(fā)抑制的邏輯,我們的基本邏輯和開(kāi)源庫(kù)golang.org/x/sync/singleflight沒(méi)有區(qū)別,只是singleflight內(nèi)部實(shí)現(xiàn)更加嚴(yán)謹(jǐn)

直接使用singleflight非常簡(jiǎn)單的就可以實(shí)現(xiàn)我們的訴求

  • singleflight.Group 創(chuàng)建一個(gè)需要并發(fā)控制的范圍
  • Do函數(shù)

第一個(gè)參數(shù)接收一個(gè)key來(lái)判斷否重復(fù)調(diào)用

第二個(gè)參數(shù)為要執(zhí)行的函數(shù),函數(shù)可以返回正常值或者error

Do函數(shù)返回值除了閉包函數(shù)的返回值之外,還返回了此次返回值是否由其他goroutine共享

import (
 "golang.org/x/sync/singleflight"
)

var g = new(singleflight.Group)

func search(ctx context.Context, word string) (string, error) {
 resp, err, _ := g.Do(word, func() (interface{}, error) {
  return query(ctx, word)
 })

 return resp.(string), err
}

錯(cuò)誤處理

因?yàn)楣蚕淼谝粋€(gè)goroutine的結(jié)果,因此如果第一次調(diào)用失敗,那其他goroutine也都會(huì)失敗

如果在某些場(chǎng)景下允許第一個(gè)調(diào)用失敗后再次嘗試調(diào)用該函數(shù),那么可以通過(guò)調(diào)用Forget方法來(lái)忘記這個(gè)key

var g = new(singleflight.Group)

func search(ctx context.Context, word string) (string, error) {
 resp, err, _ := g.Do(word, func() (interface{}, error) {
  val, err := query(ctx, word)
  // 當(dāng)出錯(cuò)并且允許重試時(shí)
  if err != nil && true {
   g.Forget(word)
   return "", err
  }

  return val, err
 })

 return resp.(string), err
}

超時(shí)控制

當(dāng)使用Do函數(shù)時(shí),如果query長(zhǎng)時(shí)間未響應(yīng)(這里假設(shè)qeury不具備超時(shí)能力),那么所有的goroutine都會(huì)被阻塞并等待,利用DoChan+select可以實(shí)現(xiàn)超時(shí)邏輯

var g = new(singleflight.Group)

func search(ctx context.Context, word string) (string, error) {
    ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
 defer cancel()
    
 result := g.DoChan(word, func() (interface{}, error) {
  return query(ctx, word)
 })

 select {
 case r := <-result:
  return r.Val.(string), r.Err
 case <-ctx.Done():
  return "", ctx.Err()
 }
}

使用場(chǎng)景

預(yù)防緩存穿透

在高并發(fā)的狀態(tài)下,一般會(huì)給熱點(diǎn)數(shù)據(jù)設(shè)置緩存。但數(shù)據(jù)第一次訪問(wèn)或者緩存失效的狀態(tài)下,如果直接去查詢(xún)數(shù)據(jù)庫(kù),會(huì)給數(shù)據(jù)庫(kù)造成極大壓力,甚至直接打爆數(shù)據(jù)庫(kù)。

以上各種分享中被反復(fù)提到的場(chǎng)景,但!注意!使用singleflight就一勞永逸了么,不是的,在大規(guī)模集群下可能有數(shù)百臺(tái)機(jī)器,當(dāng)處在高并發(fā)狀態(tài)時(shí),即使每臺(tái)機(jī)器只發(fā)起一個(gè)請(qǐng)求,也足以打爆你的數(shù)據(jù)庫(kù)!結(jié)合實(shí)際,搭配適當(dāng)?shù)木彺娌呗浴?shù)據(jù)預(yù)熱、限流等手段才能避免潛在的風(fēng)險(xiǎn)。挖個(gè)坑,以后有機(jī)會(huì)聊聊這些問(wèn)題

總結(jié)

本篇作為一個(gè)例子,給你講透典型的Go并發(fā)控制的姊妹篇,講述了另外一種并發(fā)控制模型,并介紹了開(kāi)源庫(kù)golang.org/x/sync/singleflight。

當(dāng)由一個(gè)goroutine并發(fā)向下發(fā)展成多個(gè)goroutine時(shí),使用golang.org/x/sync/errgroup

當(dāng)多個(gè)goroutine并發(fā)向下抑制成一個(gè)goroutine時(shí),使用golang.org/x/sync/singleflight

責(zé)任編輯:武曉燕 來(lái)源: 涼涼的知識(shí)庫(kù)
相關(guān)推薦

2023-01-30 15:41:10

Channel控制并發(fā)

2017-08-21 10:56:55

MySQL并發(fā)控制

2022-10-17 08:07:13

Go 語(yǔ)言并發(fā)編程

2022-12-12 09:07:06

Redis并發(fā)限流

2009-09-24 14:43:53

Hibernate樂(lè)觀

2021-07-28 08:32:58

Go并發(fā)Select

2025-06-03 02:00:00

2024-04-30 10:29:46

前端開(kāi)發(fā)h5開(kāi)發(fā)函數(shù)

2023-12-21 07:09:32

Go語(yǔ)言任務(wù)

2023-12-29 08:10:41

Go并發(fā)開(kāi)發(fā)

2021-07-06 14:47:30

Go 開(kāi)發(fā)技術(shù)

2021-07-30 07:28:15

WorkerPoolGo語(yǔ)言

2016-10-28 17:39:47

phpgolangcoroutine

2024-07-08 00:01:00

GPM模型調(diào)度器

2025-06-17 09:32:15

2023-11-27 18:07:05

Go并發(fā)編程

2025-05-22 09:01:28

2025-01-15 09:13:53

2023-02-10 09:40:36

Go語(yǔ)言并發(fā)

2013-05-28 09:43:38

GoGo語(yǔ)言并發(fā)模式
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

亚洲精品美女免费| 天天躁日日躁狠狠躁欧美| 亚洲欧洲视频| 国语自产精品视频在线看| 男人的天堂免费在线视频| 欧美色图天堂网| a级毛片免费观看在线| 日韩欧美一二三四区| 亚洲视频一二三| 在线黄色av| 亚洲影院天堂中文av色| 美女少妇精品视频| 国产成人77亚洲精品www| 精品日本一区二区三区| 日本在线不卡视频一二三区| 北条麻妃高清一区| 日韩电影在线一区二区三区| 大陆极品少妇内射aaaaaa| 久久久久久夜精品精品免费| 成人免费看黄网址| 自拍偷拍亚洲激情| 一本一道波多野毛片中文在线| 欧美福利电影网| 日韩在线播放一区| 伊人久久亚洲| 成人写真福利网| 免费看的黄色欧美网站| 91黄色国产视频| 911精品美国片911久久久| 日韩欧美一区电影| 日韩免费一区二区三区| 久久爱.com| 欧美撒尿777hd撒尿| 婷婷综合影院| 日韩一二区视频| 久久久久久久久久久久久久久99 | 日韩精品国产欧美| 尤物av一区二区| 在线观看污视频| 精品一区亚洲| 91免费视频网址| 亚洲成人在线观看视频| 日产精品高清视频免费| 在线观看欧美理论a影院| 亚洲va久久久噜噜噜| 日韩免费特黄一二三区| 奇门遁甲1982国语版免费观看高清 | 久久―日本道色综合久久| 麻豆md0077饥渴少妇| 久久在线91| 色综合视频二区偷拍在线| 亚洲一级毛片| 国产伊人精品在线| 精品一区二区三区在线视频| 亚洲一区三区| 麻豆国产精品777777在线| 日本 国产 欧美色综合| 日韩在线中文字| 美女100%一区| 国产精品吊钟奶在线| 精品国产一区二区三区四区阿崩| 久久中文字幕在线| 视频一区国产| av在线亚洲男人的天堂| 国产最新精品免费| 成人免费观看www在线| 在线一区二区三区四区五区 | 在线一区视频观看| 日韩欧美国产三级电影视频| 欧美xxxxhdvideosex| 人妖精品videosex性欧美| 国产色噜噜噜91在线精品| 欧美不卡视频一区发布| 中文字幕亚洲在线观看| av一区二区三区免费| 奇米影视一区二区三区小说| 国产免费色视频| 中文一区二区在线观看| 中文字幕亚洲激情| 亚洲精品在线播放| 国产精品免费一区二区三区在线观看 | 欧美性猛交xxx乱久交| 国产一区二区毛片| a在线视频观看| 亚洲欧洲精品天堂一级| 精品sm捆绑视频| 综合图区亚洲| 欧美亚洲国产成人精品| 国产一区二区三区黄视频| 国外男同性恋在线看| www.久久色.com| 日本视频在线一区| 极品美乳网红视频免费在线观看| 国产精品自在线拍| 六月婷婷久久| 在线观看91av| 91视频综合| 日韩在线三区| 国产精品久久久久久妇女6080| 欧美最新大片在线看| 亚洲 欧美 另类人妖| 欧美日韩欧美一区二区| 国产在线不卡| 图片区亚洲欧美小说区| 国产伦精品一区二区三区免费 | 日本www.色| 欧美大片一区二区三区| 亚洲视频分类| 精品这里只有精品| 精品爽片免费看久久| 亚洲人metart人体| 北条麻妃在线视频观看| 亚洲承认视频| 久99久在线视频| 久久久精品国产99久久精品芒果| 91福利区在线观看| 九九久久九九久久| 精品久久久久久久久久久久包黑料 | 久久综合图片| 麻豆精品在线观看| 国产一级大片免费看| 色婷婷精品久久二区二区蜜臀av| 精品精品国产三级a∨在线| 97久久精品人人做人人爽50路| 午夜精品一区二区三区三上悠亚| 国产大片在线免费观看| 国产精品高潮呻吟| 青草热久免费精品视频| 欧美激情在线观看视频| 91精品办公室少妇高潮对白| 日韩精品资源二区在线| 女厕嘘嘘一区二区在线播放 | 99久久综合狠狠综合久久| www污污在线| 亚洲男人天堂网站| 日本不卡中文字幕| 乱人伦中文视频在线| 国内精品二区| 欧美一区二区视频网站| 性色av一区二区怡红| 激情在线小视频| 国产一区不卡在线观看| 欧美日韩你懂得| 在线亚洲观看| 51xtv成人影院| 在线视频不卡一区二区| 在线观看日韩av| 久久亚洲二区三区| 亚洲欧洲精品在线 | 在线播放你懂得| 999日本视频| 欧美一区二区三区电影| 一本色道久久综合亚洲精品不| 91丝袜高跟美女视频| 蜜桃av噜噜一区| 国产日韩精品一区二区浪潮av| 91玉足脚交白嫩脚丫在线播放| 国产精品调教视频| 999一区二区三区| 久久久久久久波多野高潮日日| 91精品免费久久久久久久久| 午夜精品久久久久久久99樱桃| 欧美日韩爆操| 国产丝袜在线观看视频| 69sex久久精品国产麻豆| 91精品国产777在线观看| 一本大道av伊人久久综合| 奇米影视7777精品一区二区| 91精品视频在线播放| 欧美日韩一区二区免费在线观看| 女人色偷偷aa久久天堂| 在线天堂中文资源最新版| 男女无套免费视频网站动漫| 色综久久综合桃花网| 国产乱人伦精品一区二区在线观看 | 亚洲国产中文字幕久久网| 91网页版在线| 亚洲春色h网| 国产三区在线观看| 国产91在线亚洲| 国产精品情侣自拍| 欧美xingq一区二区| 丁香激情综合国产| 国产精品久久久久蜜臀| 精品51国产黑色丝袜高跟鞋| 国产免费一区二区三区视频| 国产99在线播放| 亚洲深夜福利在线| 欧美日韩国产区| 久草中文综合在线| 国际精品欧美精品| 色操视频在线| 国产v片免费观看| 椎名由奈av一区二区三区| 日韩综合在线| 拍真实国产伦偷精品| 国产一区一区三区| 久久久久久久久久久亚洲| 日本韩国一区二区| 激情综合色综合久久|