前言

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