1.2.2 写管道可以不阻塞协程吗
参考1.2.1小节的例子,由于异步协程执行非核心逻辑的耗时较长,因此当请求访问量较大时,队列(管道)中的数据可能会积压,极端情况下甚至会阻塞请求处理协程的写入操作。那怎么办呢?能不能创建多个异步协程来处理数据呢?当然可以,这样可以在一定程度上避免数据的积压。但是,仍然无法完全避免此问题,也就是说仍然有可能会阻塞请求处理协程的写入操作。这是肯定不能接受的,还有其他办法吗?比如管道的写入操作能不能不阻塞用户协程呢?
在Go语言中,可以通过select+default的组合实现管道的非阻塞式操作,我们基于这种方案改造一下1.2.1小节中请求处理协程向队列写入数据的逻辑,如下所示:
编译并运行改造后的程序,随后通过ab压测工具模拟大量并发请求,命令如下:
你会发现,ab压测很快就结束了,也就是说Go服务很快就处理完了这1000个请求,并没有出现1.2.1小节中的请求处理协程被阻塞的情况。
最后再思考一下:这个过程是如何实现的呢?为什么在使用了select与default之后,写管道就不会再阻塞请求处理协程呢?其实,Go语言编译器在编译阶段会将select+default的组合转化为下面的代码:
参考上面的代码,函数selectnbsend用于非阻塞地向管道写入数据,当管道不可写时(比如容量已经满了),函数selectnbsend返回false,此时执行default分支。
当然,通过非阻塞方式向队列写入数据也并不是完美的方案。毕竟我们的初衷是将非核心逻辑异步化处理,但是这种方案可能导致请求处理协程写入数据失败,如果不做处理的话可能会导致数据丢失。为了解决数据丢失问题,我们可以采取一些补救措施,比如直接执行非核心逻辑(但是这样会导致请求处理过程变慢),或者也可以先记录日志,然后通过其他方式进行补救。