1. for-select循环:
使用方式:for+select
for { // loop select { // use channel } }
eg1: 非抢占式任务
for { // loop select { case <- done: return default:
// 进行非抢占任务 } }
eg2: 迭代发送值
for _, s := range []string{"t","e","s","t"}{ // loop select { case <-done: return case stringStream<-s: } }
2. 防止goroutine泄露的几种实践
1.3版本后,golang引入了较好的GC机制,并且在不断的对其完善,解决了内存泄露的问题。但是没有解决goroutine泄露的情况。
goroutie的最佳实践应该是,开的goroutine都应该让它正确的结束,否则就意味着goroutine leak,给goroutine分配的内存以及它所引用的内存都将无法被回收。
一个典型的造成goroutine泄露的代码:
func doWork(strings <-chan string) <-chan interface{}{ completed := make(chan interface{}) go func() { defer close(completed) for s := range strings{ // loop completed <- s } }() return completed } func main() { // nil channel doWork(nil) }
上述代码中,doWork()函数中开了一个goroutine对 strings channel做一些操作,操作完成后返回。在main()中对其调用,但是传的参数strings是nil。golang中对nil channel的读取是阻塞的。因此,doWork()函数实际上被阻塞。主goroutine执行结束后,doWork()仍然在阻塞,就造成了goroutine泄露。
因此,在使用goroutine时,还是要考虑正确的关闭当前的goroutine。参考了官方文档,goroutine有以下几种方式被终止:
(1) 当它完成了当前的工作
(2) 因为不可恢复的错误,不能继续工作
(3) 当它被告知需要终止工作
对于第一种方法,goroutine完成了当前的工作后自然会进行返回,对于第二种方法,goroutine会对抛出异常,因此我们用第三种方法来进行实践。
方法1:在发送端关闭channel,channel关闭后,接收端不在接收
func main() { strings := make(chan string,10) // sender go func() { for{ select { case <-time.After(10*time.Second): close(strings) case strings<-"sender": } } }() // receiver go func() { for value := range dataCh { log.Println(value) } }() time.Sleep(20*time.Second) }
方法2:借助channel的特性,读取一个空的channel会阻塞,读取已经关闭的channel会返回类型的零值(引入额外的开销)
我们对上述的代码修改,
// param: done channe interface{}, strings <- chan string func doWork(done <-chan interface{},strings <-chan string) <-chan interface{}{ completed := make(chan interface{}) go func() { defer close(completed) for { // loop select { case s:=<-strings: completed <- s case <-done: // done 为nil时,阻塞 外部关闭done后,可以读取到值,return return } } }() return completed } func main() { done := make(chan interface{}) strings := make(chan string) strings <- "11111" doWork(done,strings) go func() { // 关闭done channel close(done) }() }
方法3:sync.Context包(管理上下文)
Context包中封装了多个函数,几个常用的有:WithCancel,WithTimeout,WithValue。我们可以使用其中的WithCancel()来防止goroutine泄露。
对上述代码进行改写:
// param: ctx context.Context, strings <- chan string func doWork(ctx context.Context ,strings <-chan string) <-chan interface{}{ completed := make(chan interface{}) go func() { defer close(completed) for { // loop select { case s:=<-strings: completed <- s case ctx.Done(): return } } }() return completed } func main() { ctx, cancel := context.WithCancel(context.Background()) strings := make(chan string) strings <- "11111" doWork(ctx,strings) time.Sleep(10*time.Second) // close context cancel() }
本质上仍是在发送端对其进行了关闭。