當我們需要管理 有多少goroutine可以同時訪問資源,使用訊號量是一種可靠的方法。
可以使用緩衝通道建立一個訊號量,其中通道的大小決定了可以同時執行多少個goroutine:
- 一個goroutine傳送一個值到通道中,佔用一個槽。
- 在完成任務後,它會刪除該值,從而為另一個goroutine釋放該插槽。
訊號量 程式碼:
<font>// nonbinary/counting semaphores<i> // 用於有多個讀者和一個寫者的讀者-寫者鎖。<i> type Semaphore struct { sem chan struct{ id int } timeout time.Duration // how long to wait to acquire the semaphore before giving up<i> }
|
<font>// 在獲取時推送給 Chan,表示我們正在使用可用資源<i> func (s *Semaphore) semaAcquire(id int) error { timer := time.NewTimer(s.timeout)
select { case s.sem <- struct{ id int }{id: id}: timer.Stop() return nil case <-timer.C: fmt.Println("deadline exceeded in acquiring the semaphore!") return ErrNoTickets } }
|
Go中semaphore是一個加權的訊號量。 加權訊號量允許一個goroutine吃多個槽,這在任務資源消耗不同的場景中很有用。 例如,管理一個資料庫連線池,其中某些操作可能需要同時使用多個連線。
下面是完整程式碼:
package main
import ( <font>"errors" "fmt" "time" )
var ErrNoTickets = errors.New("semaWait deadline exceeded!")
// nonbinary/counting semaphores<i> // used in reader-writer locks where we have multiple readers and a single writer.<i> type Semaphore struct { sem chan struct{ id int } timeout time.Duration // how long to wait to acquire the semaphore before giving up<i> }
// push to the chan on acquire denoting we are utilising the available resource<i> func (s *Semaphore) semaAcquire(id int) error { timer := time.NewTimer(s.timeout)
select { case s.sem <- struct{ id int }{id: id}: timer.Stop() return nil case <-timer.C: fmt.Println("deadline exceeded in acquiring the semaphore!") return ErrNoTickets } }
// just consume from the channel<i> func (s *Semaphore) semaRelease() { ID := <-s.sem fmt.Printf("releasing the lock held by :%d\n", ID.id) }
func (s *Semaphore) IsEmpty() bool { return len(s.sem) == 0 }
func semaInit(count int, timeout time.Duration) *Semaphore { sema := &Semaphore{ sem: make(chan struct{ id int }, count), timeout: timeout, } return sema }
|
測試程式碼:
package main
import ( <font>"sync" "testing" "time" )
func TestSemaDeadlineExceeded(t *testing.T) { // 用 4 張可用票啟動 5 個 goroutines<i> //每個 goroutines 只休眠 1 秒鐘。第 5 個程式應在超過截止時間後出錯<i> var wg sync.WaitGroup n := 4 sema := semaInit(n, 50*time.Millisecond)
for i := 0; i < n; i++ { wg.Add(1) go func(id int) { //只要有 n 張門票可用,就可以同時訪問資源<i> // 例如,在多讀者、單寫入器中,只要沒有寫入器處於活動狀態,就有 N 個讀者可以讀取資源。<i> defer wg.Done()
// acquire the resource<i> if err := sema.semaAcquire(id); err != nil { t.Error(err) return }
// do the work<i> time.Sleep(2 * time.Second)
// release the semaphore<i> sema.semaRelease()
}(i + 1) }
time.Sleep(1 * time.Second) //最後一張開始得有點晚,理想情況下應該超時,因為所有門票都已消費完畢,正在進行結算。<i>
wg.Add(1) go func() { defer wg.Done() if err := sema.semaAcquire(5); err != ErrNoTickets { t.Error(err) return } }()
wg.Wait()
}
|