Go 併發可視化解釋 - Semaphore

在這個系列的最後兩篇文章中,我們討論了來自sync包的MutexRWMutex。當我們希望只有一個 Goroutine 能夠獨佔地訪問共享數據時,這兩個結構非常有用。

然而,在現實生活中,有些用例需要允許多個用戶同時訪問共享資源。這個數量可以很大,也可以很小,但必須始終是有限的。例如,一個容納 60000 人的體育場在任何時候都不應容納超過這個數量的人。或者,在機場,無論乘客隊列有多長,同時允許辦理登機手續的最大乘客數量就是開放櫃檯的數量。在計算機科學中,這種併發訪問的用例是用**信號量(Semaphore)來建模的。在本文中,我將通過可視化的方式解釋信號量(Semaphore)的工作原理。我還將與您分享如何在 Golang 中使用通道來簡單實現信號量(Semaphore)**的方法。

場景

有 4 個 Gopher 想去游泳。然而,只有 2 個游泳道。每個泳道最多允許 1 個 Gopher 在任何時候游泳,但他們可以輪流游泳。

Candier 想去游泳。因爲兩個泳道都可用,所以她可以立即獲得一個泳道。

片刻之後,Partier 和 Swimmer 也想去游泳。此時只有一個泳道可用。只有其中一個可以獲得泳道,另一個必須等待。當 M 個 Goroutine 競爭 N(N <M)個槽位時,我們不能保證誰會贏得這個 “競爭”。在這種情況下,當 M=2N=1 時,假設 Swimmer 贏了。

Swimmer 非常擅長這項運動。他迅速完成了他的輪次,並迅速釋放了泳道,使其可用於 Partier。與此同時,Candier 仍然在她的泳道上游泳。

Stringer 想去游泳,但兩個泳道目前都被佔用。他別無選擇,只能等待。他不知道也不關心哪一條泳道會先可用。

假設 Partier 在這項運動中也比 Candier 更有天賦。儘管在 Candier 之後開始,但仍然比她早完成。Partier 釋放了他的泳道,使其可用於 Stringer。

不久後,Candier 完成並釋放了她的泳道。泳道變得可用,但沒有人試圖佔用它,它仍然可用。

最後,Stringer 完成了他的輪次。他釋放了他的泳道,使兩個泳道都可用。

面試

在技術面試中,Semaphore 這個術語可能聽起來有點嚇人。然而,正如你在上面的插圖中所看到的,它是非常容易理解的。事實上,我曾幾次在面試中問過我的面試者關於 Semaphore 的問題。例如,設計一個在黑色星期五上線的虛擬商店。有很多顧客想進去,但商店最多隻能容納 N 個顧客。每個顧客進去後沒有時間限制,他 / 她可以一整天待在店裏,也可以一進去就離開。當已經有 N 個顧客在裏面時,後來的顧客必須排隊等候,直到有人離開。當然,有很多解決這個問題的方法,其中沒有一個是對或錯的。我希望這篇文章爲您提供了處理 N 個併發訪問問題的另一種工具。

真實世界的例子

辦理登機手續櫃檯、體育場的座位以及計算機資源,如 CPU、內存和網絡,有一個共同點:它們都是有限的。通常,控制應用程序資源使用是一個好主意。我曾在我的一個應用程序中使用 Semaphore 來限制一次只能有限數量的併發資源密集型 Goroutine。這也可以通過一個包含 N 個 Goroutine 的池來解決。然而,由於我們的併發工作負載不是均勻分佈在時間上的,它可能在 0 和 N 之間的任何位置,所以我們發現 Semaphore 是一個更好的選擇。

展示你的代碼!

請注意,Semaphore 並不像sync.Mutex一樣作爲內置組件提供。相反,Go 團隊將其作爲擴展提供。將其添加到項目中非常簡單:go get golang.org/x/sync

package main
import (
    "context"
    "golang.org/x/sync/semaphore"
    "log"
    "time"
)
func main() {
    pool := semaphore.NewWeighted(2)
    go swim("Candier", pool)
    go swim("Swimmer", pool)
    go swim("Partier", pool)
    go swim("Stringer", pool)
    time.Sleep(5 * time.Second) // For brevity, better use sync.WaitGroup
    log.Println("Main: Done, shutting down")
}
func swim(name string, pool *semaphore.Weighted) {
    log.Printf("%v: I want to swim\n", name)
    // In real applications, pass in your context such as HTTP request context
    ctx := context.Background()
    // We can also Acquire/Release more than 1
    // when the workloads consume different amount of resources
    if err := pool.Acquire(ctx, 1); err != nil {
        log.Printf("%v: Ops, something went wrong! I cannot acquire a lane\n", name)
        return
    }
    log.Printf("%v: I got a lane, I'm swimming\n", name)
    time.Sleep(time.Second)
    log.Printf("%v: I'm done. Releasing my lane\n", name)
    pool.Release(1)
}

實現自己的 Semaphore

如果您不想將golang.org/x/sync/semaphore添加到項目中,使用通道自己實現 Semaphore 也相當簡單。

type Semaphore struct {
    ch chan bool
}
func NewSemaphore(weight int) *Semaphore {
    return &Semaphore{
        ch: make(chan bool, weight),
    }
}
func (s *Semaphore) Acquire() {
    s.ch <- true
}
func (s *Semaphore) Release() {
    <-s.ch
}}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/-QqvpMB61nScBSUkVteRUw