golang執行緒池在IO多路複用中的應用

hl4080發表於2020-10-11

前言

之前介紹過基於reactor模式的IO多路複用技術,reactor模式本質上就是迴圈監聽-處理的過程,當處理過程代價很小(比如echo伺服器),服務端實際上長期阻塞於監聽環節,這樣會導致客戶端感覺自己的請求是被立即處理的。如果需要服務端支援IO阻塞型應用,單執行緒的reactor模式就顯得不太適合了,因為某個客戶端會長期佔據IO而使得其餘客戶端的請求無法及時得到響應。這時候可以對這部分請求單獨分配執行緒池進行處理,以保證輕量的請求不被阻塞。

golang執行緒池

傳統的執行緒池在golang中可以用更為輕量的協程池代替,具體的做法是分配一定數量的協程,然後利用chan資料結構,將特定的資料通過chan傳入到協程池中進行處理。同時設定一個終止的標誌,可以選擇在一個請求之後、一段時間之後設定這個標誌來終止協程,或者保持這個協程池一直處於執行狀態來持續處理客戶端的請求,我們這裡設定在一個請求完成之後就終止當前協程

type Pool struct {
	req 	chan interface{}	//待處理的請求
	done	chan bool			//完成標誌
	number 	int					//協程池的大小
}
func (p *Pool) Init(num int) {
	p.req = make(chan interface{}, MaxRequestNum)
	p.done = make(chan bool, num)
	p.number = num
}

func (p *Pool) start() {
	for i := 0; i < p.number; i++ {
		go func(id int) {
			fmt.Printf("worker %d started!\n", id)
			for {
				select {
				case t := <-p.req:
					err := download(t.(string))
					if err != nil {
						fmt.Println(err)
					}
					p.done <- true
				case <-p.done:
					fmt.Printf("worker %d stopped!\n", id)
					return
				}
			}
		}(i)
	}
}

最後,我們只需要在主功能程式中整合該協程池的程式碼塊,即可非同步處理特定的IO請求(設計不完善,對於這部分功能,我們沒有實現與客戶端的互動請求,只實現到簡單的巢狀步驟)。

//io密集型的下載任務交由執行緒池內單獨的執行緒完成,其餘任務仍由主執行緒完成,這部分程式碼可以集合成一個單獨的模組非同步處理下載請求
	{
		urls := []string{"https://down.qq.com/qqweb/PCQQ/PCQQ_EXE/PCQQ2020.exe","https://down.qq.com/qqweb/PCQQ/PCQQ_EXE/PCQQ2020.exe","https://down.qq.com/qqweb/PCQQ/PCQQ_EXE/PCQQ2020.exe"}
		p := new(Pool)
		p.Init(MaxPoolNum)
		p.start()
		time.Sleep(1*time.Second)
		for i := 0; i < len(urls); i++ {
			url := urls[i]
			p.req <- url
		}
		close(p.req)
	}

相關文章