Go语言协程使用
前言
go语言的真正精髓,莫过于go协程和channel.因此对于goroutine和channel的正确使用,非常重要.是编写高并发程序的重要基础.本文试图将协程的使用方方面面讲清楚.
全文阅读大概需要30分钟.如时间不够,欢迎收藏后阅读.
协程的使用及控制
在Go语言中开一个协程非常方便,在需要通过协程来执行的函数时,直接在函数前加go关键字就可以
执行后输出
程序正常执行没有报错,但是没有函数A的输出.
这是因为主协程并不会等待子协程执行完才往下走,执行到go后,语句会继续执行,go后面的函数新开一个协程,各跑各的,所以主协程执行完go语句,就无事可做,就退出了.
那怎么让上面的代码打印出函数A的输出.得让主函数待一会儿协程执行完了再退出,或者让主协程不退出,比如在web程序中,主协程是不退出的.
- 通过sync. WaitGroup的三个方法 Add(), Done(), Wait() 来实现协程的控制
- 通过带buffer的channel来控制
- 通过sync. Cond
下面演示下等待协程完成的代码
下面演示通过channel来控制协程的流程
下面演示通过sync. Cond来实现
协程崩溃处理
在Go语言中,如果一个协程崩溃了,则所有协程都会退出,比如数组越界,会触发panic(相当于throw exception), 这对持续可运行的应用来说,显然不是我们想要的效果.那这个时候我们需要对崩溃进行修复.在Go语言中提供了一个defer和recover来实现崩溃恢复,这个相当于其它语言的try catch的方式.
在使用recover函数时,如果要达到能捕获异常的作用,有几点需要注意:
- recover如果想起作用的话, 必须在defered函数前声明,因为只要panic,后面的函数不会被执行
- recover函数只有在方法内部发生panic时,返回值才不会为nil,没有panic的情况下返回值为nil
下面用代码示例来说明情况
输出:
再看在panic前声明recover
输出
此时的panic被捕获了
defer recover函数必须放在需要捕获panic的函数前面
因此本示例如果将defer recover放在go func函数中被调用函数f前面,也能捕获住A函数的panic
输出
因此,如果在协程内执行其它函数时,为了保证不崩溃,安全的做法是,提前声明defer recover函数
这样可以保证协程内部崩溃,不会将整个进程崩溃掉
协程超时控制
当你希望控制一个协程的执行时间,如果超过指定时间,还没有执行完,则退出.直接返回超时错误,这个该如何做呢?
通行做法是用select + channel来进行超时控制,
channel发执行完毕的信号,然后超时信号通用ctx. Done()或者time. After(), 或者time. Ticket()来完成超时通知退出,select捕获到其中一个channel有数据,就执行对应的代码,然后退出.
其中且个注意的点是, channel要用有缓冲的,不然,在超时分支退出时,协程还在卡住,造成goroutine泄露.
代码示例如下
输出如下:
程序执行了超时退出
是否可以无限多开协程
众所周知,协程不同于线程,并不和操作系统的线程有具体的对应关系.协程是由go的一个线程池来调度的.
go runtime并不会产生一个协程对应产生一个os线程,是一个m:n的对应关系,根据m:n对应关系,协程对应的os线程runtime. GOMAXPROCS默认为系统逻辑cpu数量,因此创建更多的m并不会产生更多的操作系统线程,但是可以通过runtime. GOMAXPROCS()来设置当前程序运行时占用的系统核心数
协程创建需要占用一定量的内存,开一个协程只需要少量的内存空间,几KB,这也是golang能实现百万长链的原因.
但在实际中,协程需要正确的关闭,而不是无限创建后,造成协程泄露,进而引发系统崩溃.
高并发下情况下如何开协程
在高并发情况下,需要通过一个带缓冲的channel的来实现对于协程的创建数量进行控制,进而实现一个健康稳定的可持续运行的高并发处理程序.
结尾
现代语言的发展,从进程到线程,进而到协程.各种语言的诞生,一直致力于2个方面的努力.
- 更高效率的利用CPU
- 更低成本的实现并发
目前来看,Go语言的协程则是这2个思路的最佳实践.go的协程和channel是Go语言的真正精髓.掌握好go+channel的使用,有利于更加准确的开发高效的并发处理程序.
全文完,感谢您的阅读.
如有不正确的地方,欢迎留言讨论.