亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go語言并發(fā)之Sync包的6個關(guān)鍵概念總結(jié)

 更新時間:2023年05月17日 09:42:17   作者:洛天楓  
這篇文章主要為大家詳細介紹了Go語言并發(fā)中Sync包的6個關(guān)鍵概念,文中的示例代碼講解詳細,對我們深入學(xué)習(xí)Go語言有一定的幫助,需要的可以參考一下

1.sync.Mutex和sync.RWMutex

要知道,mutex(互斥)對于我們 gopher 來說就像一個老伙計。在處理 goroutine 時,確保它們不會同時訪問資源是非常重要的,而 mutex 可以幫助我們做到這一點。

sync.Mutex

看看這個簡單的例子,我沒有使用互斥鎖來保護我們的變量 a:

var a = 0
func Add() {
  a++
}
func main() {
  for i := 0; i < 500; i++ {
    go Add()
  }
  time.Sleep(5 * time.Second)
  fmt.Println(a)
}

此代碼的結(jié)果是不可預(yù)測的。如果幸運的話,您可能會得到 500,但通常結(jié)果會小于 500?,F(xiàn)在,讓我們使用互斥體增強我們的 Add 函數(shù):

var mtx = sync.Mutex{}
func Add() {
  mtx.Lock()
  defer mtx.Unlock()
  a++
}

現(xiàn)在,代碼提供了預(yù)期的結(jié)果。但是使用 sync.RWMutex 呢?

為什么使用 sync.RWMutex

想象一下,您正在檢查 a 變量,但其他 goroutines 也在調(diào)整它。您可能會得到過時的信息。那么,解決這個問題的方法是什么?

讓我們退后一步,使用我們的舊方法,將 sync.Mutex 添加到我們的 Get() 函數(shù)中:

func Add() {
  mtx.Lock()
  defer mtx.Unlock()
  a++
}
func Get() int {
  mtx.Lock()
  defer mtx.Unlock()
  return a
}

但這里的問題是,如果您的服務(wù)或程序調(diào)用 Get() 數(shù)百萬次而只調(diào)用 Add() 幾次,那么我們實際上是在浪費資源,因為我們大部分時間甚至都沒有修改它而將所有內(nèi)容都鎖定了。

這就是 sync.RWMutex 突然出現(xiàn)來拯救我們的一天,這個聰明的小工具旨在幫助我們處理同時讀取和寫入的情況。

var mtx = sync.RWMutex{}
func Add() {
  mtx.Lock()
  defer mtx.Unlock()
  a++
}
func Look() {
  mtx.RLock()
  defer mtx.RUnlock()
  fmt.Println(a)
}

那么,RWMutex 有什么了不起的呢?好吧,它允許數(shù)百萬次并發(fā)讀取,同時確保一次只能進行一次寫入。讓我澄清一下它是如何工作的:

  • 寫入時,讀取被鎖定。
  • 讀取時,寫入被鎖定。
  • 多次讀取不會相互鎖定。

sync.Locker

哦對了,Mutex和RWMutex都實現(xiàn)了sync.Locker接口{},簽名是這樣的:

// A Locker represents an object that can be locked and unlocked.
type Locker interface {
  Lock()
  Unlock()
}

如果你想創(chuàng)建一個接受 Locker 的函數(shù),你可以將這個函數(shù)與你的自定義 locker 或同步互斥鎖一起使用:

func Add(mtx sync.Locker) {
  mtx.Lock()
  defer mtx.Unlock()
  a++
}

2. sync.WaitGroup

您可能已經(jīng)注意到我使用了 time.Sleep(5 * time.Second) 來等待所有 goroutine 完成,但老實說,這是一個非常丑陋的解決方案。

這就是 sync.WaitGroup 出現(xiàn)的地方:

func main() {
  wg := sync.WaitGroup{}
  for i := 0; i < 500; i++ {
    wg.Add(1)
    go func() {
      defer wg.Done()
      Add()
    }()
  }
  wg.Wait()
  fmt.Println(a)
}

sync.WaitGroup 有 3 個主要方法:Add、Done 和 Wait。

首先是 Add(delta int):此方法將 WaitGroup 計數(shù)器增加 delta 的值。你通常會在生成 goroutine 之前調(diào)用它,表示有一個額外的任務(wù)需要完成。

如果我們將 WaitGroup 放在 go func() {} 中,您認為會發(fā)生什么?

go func() {
  wg.Add(1)
  defer wg.Done()
  Add()
}()

我的編譯器喊道,“應(yīng)該在啟動 goroutine 之前調(diào)用 wg.Add(1) 以避免競爭”,我的運行時出現(xiàn)恐慌,“panic: sync: WaitGroup is reused before previous

Wait has returned”。

其他兩種方法非常簡單:

當(dāng)一個 goroutine 結(jié)束它的任務(wù)時, Done 被調(diào)用。

Wait 會阻塞調(diào)用者,直到 WaitGroup 計數(shù)器歸零,這意味著所有派生的 goroutine 都已完成它們的任務(wù)。

3. sync.Once

假設(shè)您在一個包中有一個 CreateInstance() 函數(shù),但您需要確保它在使用前已初始化。所以你在不同的地方多次調(diào)用它,你的實現(xiàn)看起來像這樣:

var i = 0
var _isInitialized = false
func CreateInstance() {
  if _isInitialized {
    return
  }
  i = GetISomewhere()
  _isInitialized = true
}

但是如果有多個 goroutine 調(diào)用這個方法呢? i = GetISomeWhere 行會運行多次,即使您為了穩(wěn)定性只希望它執(zhí)行一次。

您可以使用我們之前討論過的互斥鎖,但同步包提供了一種更方便的方法:sync.Once

var i = 0
var once = &sync.Once{}
func CreateInstance() {
  once.Do(func() {
    i = GetISomewhere()
  })
}

使用 sync.Once,你可以確保一個函數(shù)只執(zhí)行一次,不管它被調(diào)用了多少次或者有多少 goroutines 同時調(diào)用它。

4. sync.Pool

想象一下,你有一個池,里面有一堆你想反復(fù)使用的對象。這可以減輕垃圾收集器的一些壓力,尤其是在創(chuàng)建和銷毀這些資源的成本很高的情況下。

所以,無論何時你需要一個對象,你都可以從池中取出它。當(dāng)您使用完它時,您可以將它放回池中以備日后重復(fù)使用。

var pool = sync.Pool{
  New: func() interface{} {
    return 0
  },
}
func main() {
  pool.Put(1)
  pool.Put(2)
  pool.Put(3)
  a := pool.Get().(int)
  b := pool.Get().(int)
  c := pool.Get().(int)
  fmt.Println(a, b, c) // Output: 1, 3, 2 (order may vary)
}

請記住,將對象放入池中的順序不一定是它們出來的順序,即使多次運行上述代碼時順序也是隨機。

讓我分享一些使用 sync.Pool 的技巧:

  • 它非常適合長期存在并且有多個實例需要管理的對象,例如數(shù)據(jù)庫連接(1000 個連接?)、worker goroutine,甚至緩沖區(qū)。
  • 在將對象返回池之前始終重置對象的狀態(tài)。這樣,您可以避免任何無意的數(shù)據(jù)泄漏或奇怪的行為。
  • 不要指望池中已經(jīng)存在的對象,因為它們可能會意外釋放。

5. sync.Map

當(dāng)您同時使用 map 時,有點像使用 RWMutex。您可以同時進行多次讀取,但不能進行多次讀寫或?qū)懭?。如果存在沖突,您的服務(wù)將崩潰而不是覆蓋數(shù)據(jù)或?qū)е乱馔庑袨椤?/p>

這就是 sync.Map 派上用場的地方,因為它可以幫助我們避免這個問題。讓我們仔細看看 sync.Map 給我們提供什么:

  • CompareAndDelete (go 1.20):如果值匹配則刪除鍵的條目;如果不存在值或舊值為 nil,則返回 false。
  • CompareAndSwap(go 1.20):如果新舊值匹配,則交換一個鍵,只要確保舊值是可比較的。
  • Swap (go 1.20):交換鍵的值并返回舊值(如果存在)。
  • LoadOrStore:獲取當(dāng)前鍵值或保存并返回提供的值(如果不存在)
  • Range (f func(key, value any):遍歷映射,將函數(shù) f 應(yīng)用于每個鍵值對。如果 f 說返回 false,它會停止。
  • Store
  • Delete
  • Load
  • LoadAndDelete

Q: 我們?yōu)槭裁床皇褂脦в?Mutex 的常規(guī) map 呢?

我通常選擇帶有 RWMutex 的 map,但在某些情況下認識到 sync.Map 的強大功能很重要。那么,它真正發(fā)光的地方在哪里呢?

如果您有許多 goroutines 訪問 map 中的單獨鍵,則具有單個互斥鎖的常規(guī) map 可能會導(dǎo)致爭用,因為它僅針對單個寫操作鎖定整個 map。

另一方面,sync.Map 使用更完善的鎖定機制,有助于最大限度地減少此類場景中的爭用。

6. sync.Cond

將 sync.Cond 視為支持多個 goroutine 等待和相互交互的條件變量。為了更好地理解,讓我們看看如何使用它。

首先,我們需要創(chuàng)建帶有 Locker 的 sync.Cond:

var mtx sync.Mutex
var cond = sync.NewCond(&mtx)

goroutine 調(diào)用 cond.Wait 并等待來自其他地方的信號以繼續(xù)執(zhí)行:

func dummyGoroutine(id int) {
  cond.L.Lock()
  defer cond.L.Unlock()
  fmt.Printf("Goroutine %d is waiting...\n", id)
  cond.Wait()
  fmt.Printf("Goroutine %d received the signal.\n", id)
}

然后,另一個 goroutine(就像主 goroutine)調(diào)用 cond.Signal(),讓我們等待的 goroutine 繼續(xù):

func main() {
  go dummyGoroutine(1)
  time.Sleep(1 * time.Second)
  fmt.Println("Sending signal...")
  cond.Signal()
  time.Sleep(1 * time.Second)
}

結(jié)果如下所示:

Goroutine 1 is waiting...
Sending signal...
Goroutine 1 received the signal.

如果有多個 goroutines 在等待我們的信號怎么辦?這就是我們可以使用廣播的時候:

func main() {
  go dummyGoroutine(1)
  go dummyGoroutine(2)
  
  time.Sleep(1 * time.Second)
  cond.Broadcast() // broadcast to all goroutines
  time.Sleep(1 * time.Second)
}

結(jié)果如下所示:

Goroutine 1 is waiting...
Goroutine 2 is waiting...
Goroutine 2 received the signal.
Goroutine 1 received the signal.

到此這篇關(guān)于Go語言并發(fā)之Sync包的6個關(guān)鍵概念總結(jié)的文章就介紹到這了,更多相關(guān)Go語言Sync包內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang設(shè)計模式之外觀模式的實現(xiàn)

    Golang設(shè)計模式之外觀模式的實現(xiàn)

    這篇文章主要介紹了Golang設(shè)計模式之外觀模式的實現(xiàn),外觀模式是一種常用的設(shè)計模式之一,是一種結(jié)構(gòu)型設(shè)計模式,它提供了一個簡單的接口來訪問復(fù)雜系統(tǒng)的各種功能,從而降低了系統(tǒng)的復(fù)雜度,需要詳細了解可以參考下文
    2023-05-05
  • 重學(xué)Go語言之如何使用Context

    重學(xué)Go語言之如何使用Context

    Context,中文也叫做上下文,Go語言在1.7版本中新增的context包中定義了Context,下面我們就來一起看看如何在Go語言中使用Context吧
    2023-07-07
  • 詳解Go語言中select語句的常見用法

    詳解Go語言中select語句的常見用法

    這篇文章主要是來和大家介紹一下Go語言中select?語句的常見用法,以及在使用過程中的注意事項,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下
    2023-07-07
  • go語言題解LeetCode66加一示例詳解

    go語言題解LeetCode66加一示例詳解

    這篇文章主要為大家介紹了go語言題解LeetCode66加一示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • Gorm更新零值問題解決思路與過程

    Gorm更新零值問題解決思路與過程

    這篇文章主要介紹了Gorm更新零值問題解決思路與過程,總的來說這并不是一道難題,那為什么要拿出這道題介紹?拿出這道題真正想要傳達的是解題的思路,以及不斷優(yōu)化探尋最優(yōu)解的過程。希望通過這道題能給你帶來一種解題優(yōu)化的思路
    2023-01-01
  • 在 Golang 中實現(xiàn)一個簡單的Http中間件過程詳解

    在 Golang 中實現(xiàn)一個簡單的Http中間件過程詳解

    本文在go web中簡單的實現(xiàn)了中間件的機制,這樣帶來的好處也是顯而易見的,當(dāng)然社區(qū)也有一些成熟的 middleware 組件,包括 Gin 一些Web框架中也包含了 middleware 相關(guān)的功能,具體內(nèi)容詳情跟隨小編一起看看吧
    2021-07-07
  • Go中http超時問題的排查及解決方法

    Go中http超時問題的排查及解決方法

    這篇文章主要介紹了Go中http超時問題的排查及解決方法,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-10-10
  • Go語言使用組合的思想實現(xiàn)繼承

    Go語言使用組合的思想實現(xiàn)繼承

    這篇文章主要為大家詳細介紹了在 Go 里面如何使用組合的思想實現(xiàn)“繼承”,文中的示例代碼講解詳細,對我們學(xué)習(xí)Go語言有一定的幫助,需要的可以了解一下
    2022-12-12
  • 示例剖析golang中的CSP并發(fā)模型

    示例剖析golang中的CSP并發(fā)模型

    這篇文章主要為大家介紹了示例剖析golang中的CSP并發(fā)模型,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-05-05
  • 詳解Golang?ProtoBuf的基本語法總結(jié)

    詳解Golang?ProtoBuf的基本語法總結(jié)

    最近項目是采用微服務(wù)架構(gòu)開發(fā)的,各服務(wù)之間通過gPRC調(diào)用,基于ProtoBuf序列化協(xié)議進行數(shù)據(jù)通信,因此接觸學(xué)習(xí)了Protobuf,本文會對Protobuf的語法做下總結(jié),感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助
    2022-10-10

最新評論