Go中用緩衝通道作為訊號量限制goroutine

banq發表於2024-04-17

當我們需要管理 有多少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()

}

 

相關文章