Go程式設計模式三—Fan-Out模式與協程池結合

bytecc發表於2021-11-16

有這樣一個需求,有一批學生,現在只有Name欄位,需要根據Name欄位做引數遠端請求獲取Score欄位的值。

常規寫法

type  User  struct {
    Name  string
    Score int64
}
//模擬遠端呼叫資料
func Dodata(user *User) {
     user.Score  =  int64(len(user.Name))
}

func main() {
    s := CreatData()
    var wg sync.WaitGroup
    wg.Add(len(s)) //每個user開啟一個協程處理
    for _, item := range s {
        go func(i *User) {
            defer wg.Done()
            Dodata(i)
        }(item)
    }
    wg.Wait()
    //得到資料後下一步處理
    for _, val := range s {
        fmt.Println(val.Score)
    }
}

當user資料為n時,需要開啟n個協程去處理。程式碼不可控。能不能指定m個協程併發處理,類似協程池的思路。

func main() {
    s := CreatData()
    var wg sync.WaitGroup
    wg.Add(10)
    Handler(10, &wg, s, Dodata)//只開啟10協程去處理
    wg.Wait()
    //得到資料後下一步處理
    for _, val := range s {
        fmt.Println(val.Score)
    }
}

func Handler(number int, wg *sync.WaitGroup, s []*User, workerFun func(*User)) {
    inch := make(chan *User, 0)
    //協程1:把需要處理的引數寫入inch
    go func() {
        for _, item := range s {
            inch <- item
        }
        close(inch)
    }()
    //協程2:開啟number個協程,同時讀取inch的引數
    for i := 0; i < number; i++ {
        go func() {
            defer wg.Done()
            for item := range inch {
                workerFun(item)
            }
        }()
    }
}

假如需要讀取的結果是一個回執Receipt,則需要建立一個resCh通道,worker請求資料後把結果寫入這個通道。

//開啟指定個協程處理陣列,使用扇出方式處理
type User struct {
    Name string
}
type Receipt struct {
    Name  string
    Score int64
}
//模擬遠端呼叫資料
func Dodata(user *User) *Receipt {
    var res Receipt
    res.Name = user.Name
    res.Score = int64(len(user.Name))
    return &res
}

func main() {
    s := CreatData()
    var wg sync.WaitGroup
    var resWg sync.WaitGroup
    resCh := make(chan *Receipt)
    //協程3:開啟一個協程讀取結果
    resWg.Add(1)
    go func() {
        defer resWg.Done()
        for item := range resCh {
            fmt.Println(item.Score)
        }
    }()
    wg.Add(10)
    Handler(10, &wg, s, resCh, Dodata)
    wg.Wait()
    close(resCh) //worker結束後需要及時關閉resCh
    resWg.Wait() //保證讀取結果完整
}

func Handler(number int, wg *sync.WaitGroup, s []*User, resCh chan<- *Receipt, workerFun func(*User) *Receipt) {
    inch := make(chan *User, 0)
    //協程1:把需要處理的引數寫入inch
    go func() {
        for _, item := range s {
            inch <- item
        }
        close(inch)
    }()
    //協程2:開啟number個協程,同時讀取引數並把結果寫入resCh
    for i := 0; i < number; i++ {
        go func() {
            defer wg.Done()
            for item := range inch {
                res := workerFun(item)
                resCh <- res
            }
        }()
    }
}

FAN-OUT模式:

多個goroutine從同一個通道讀取資料,直到該通道關閉。OUT是一種張開的模式,所以又被稱為扇出,可以用來分發任務。

FAN-IN模式:

1個goroutine從多個通道讀取資料,直到這些通道關閉。IN是一種收斂的模式,所以又被稱為扇入,用來收集處理的結果。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
用過哪些工具?為啥用這個工具(速度快,支援高併發...)?底層如何實現的?

相關文章