之前介绍过基于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)
}