channel和select控制goroutines
kevin · · 2053 次点击 · · 开始浏览最近有一个需求是在一个常驻内存的程序中结束相关任务。在Go中,创建一个goroutine非常简单,只需要go一下就可以了,但是如果我创建了很多goroutine,想要结束怎么办?
比如说我有一个死循环的例子
packagemainimport("fmt""sync""time")funcmain(){queue:=make(chanint,20)varwgsync.WaitGroupfor{// 就是一个死循环queue<-1<-queuefori:=0;i<10;i++{wg.Add(1)gofunc(iint){time.Sleep(5*time.Second)fmt.Println("Sleep")wg.Done()}(i)}wg.Wait()}}如何在一个goroutine里面控制所有的goroutine,让所有的goroutine结束呢?这就需要select出场了。有人告诉我,这样子实现会更好一些:
packagemainimport("fmt""sync""time")funcmain(){// 定义一个用于指定退出的channelEXIT:=make(chanint,1)queue:=make(chanint,20)varwgsync.WaitGroup// 启动新的goroutine gofunc(){time.Sleep(10*time.Second)// 休息了之后,该结束了EXIT<-1}()for{// 进入死循环queue<-1select{case<-EXIT:// 收到了退出消息fmt.Println("KILLED")returncase<-queue:fori:=0;i<10;i++{wg.Add(1)gofunc(iint){time.Sleep(5*time.Second)fmt.Println("Sleep")wg.Done()}(i)}wg.Wait()}}}但是输出却是比较让人困惑:
F:\>gorundada.goSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepKILLED看起来停止的时间有15s,比预想的10s时间要长一些,这是为什么呢?
这里select的作用是,在遇到channel case时,尝试所有的channel是否为ready;若有一个为ready,则执行该case,多个case时会随机执行其中一个case;如果有default,则会在所有都not ready时执行;没有default的话就wait等待ready。
关于select的随机性,用一个例子来说明更方便一些:
packagemainimport("time""fmt")funcmain(){// 定义一个用于指定退出的channelc1:=make(chanint,1)c2:=make(chanint,1)for{c1<-1c2<-1select{case<-c1:fmt.Println("c1")// 防止出现panic<-c2time.Sleep(1*time.Second)case<-c2:fmt.Println("c2")<-c1time.Sleep(1*time.Second)}}}输出结果如下:
gorundada.goc1c1c2c1c1c2c1....那这样上面还是会出现那种有可能没退出的情况,这样怎么做呢?下面我个人感觉会是一种更好的做法:
packagemainimport("fmt""sync""time")funcmain(){// 定义一个用于指定退出的channelEXIT:=make(chanint,1)varwgsync.WaitGroup// 启动新的goroutine gofunc(){time.Sleep(10*time.Second)// 休息了之后,该结束了EXIT<-1}()for{select{case<-EXIT:// 收到了退出消息fmt.Println("KILLED")returndefault:fori:=0;i<10;i++{wg.Add(1)gofunc(iint){time.Sleep(5*time.Second)fmt.Println("Sleep")wg.Done()}(i)}wg.Wait()}}}还有一个值得注意的事情就是,对select来说,整体的运行相当于一个循环分支处理的过程。对case来说,过程是一个block的过程,比如说在执行default过程中,即使收到了来自EXIT的信息,也不会中断执行default去跳转执行EXIT,而是在default完成之后,进入条件分支选择使优先进入channel已经ready的case。
有疑问加站长微信联系(非本文作者)
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
关注微信- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码` - 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传
收入到我管理的专栏 新建专栏
最近有一个需求是在一个常驻内存的程序中结束相关任务。在Go中,创建一个goroutine非常简单,只需要go一下就可以了,但是如果我创建了很多goroutine,想要结束怎么办?
比如说我有一个死循环的例子
packagemainimport("fmt""sync""time")funcmain(){queue:=make(chanint,20)varwgsync.WaitGroupfor{// 就是一个死循环queue<-1<-queuefori:=0;i<10;i++{wg.Add(1)gofunc(iint){time.Sleep(5*time.Second)fmt.Println("Sleep")wg.Done()}(i)}wg.Wait()}}如何在一个goroutine里面控制所有的goroutine,让所有的goroutine结束呢?这就需要select出场了。有人告诉我,这样子实现会更好一些:
packagemainimport("fmt""sync""time")funcmain(){// 定义一个用于指定退出的channelEXIT:=make(chanint,1)queue:=make(chanint,20)varwgsync.WaitGroup// 启动新的goroutine gofunc(){time.Sleep(10*time.Second)// 休息了之后,该结束了EXIT<-1}()for{// 进入死循环queue<-1select{case<-EXIT:// 收到了退出消息fmt.Println("KILLED")returncase<-queue:fori:=0;i<10;i++{wg.Add(1)gofunc(iint){time.Sleep(5*time.Second)fmt.Println("Sleep")wg.Done()}(i)}wg.Wait()}}}但是输出却是比较让人困惑:
F:\>gorundada.goSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepSleepKILLED看起来停止的时间有15s,比预想的10s时间要长一些,这是为什么呢?
这里select的作用是,在遇到channel case时,尝试所有的channel是否为ready;若有一个为ready,则执行该case,多个case时会随机执行其中一个case;如果有default,则会在所有都not ready时执行;没有default的话就wait等待ready。
关于select的随机性,用一个例子来说明更方便一些:
packagemainimport("time""fmt")funcmain(){// 定义一个用于指定退出的channelc1:=make(chanint,1)c2:=make(chanint,1)for{c1<-1c2<-1select{case<-c1:fmt.Println("c1")// 防止出现panic<-c2time.Sleep(1*time.Second)case<-c2:fmt.Println("c2")<-c1time.Sleep(1*time.Second)}}}输出结果如下:
gorundada.goc1c1c2c1c1c2c1....那这样上面还是会出现那种有可能没退出的情况,这样怎么做呢?下面我个人感觉会是一种更好的做法:
packagemainimport("fmt""sync""time")funcmain(){// 定义一个用于指定退出的channelEXIT:=make(chanint,1)varwgsync.WaitGroup// 启动新的goroutine gofunc(){time.Sleep(10*time.Second)// 休息了之后,该结束了EXIT<-1}()for{select{case<-EXIT:// 收到了退出消息fmt.Println("KILLED")returndefault:fori:=0;i<10;i++{wg.Add(1)gofunc(iint){time.Sleep(5*time.Second)fmt.Println("Sleep")wg.Done()}(i)}wg.Wait()}}}还有一个值得注意的事情就是,对select来说,整体的运行相当于一个循环分支处理的过程。对case来说,过程是一个block的过程,比如说在执行default过程中,即使收到了来自EXIT的信息,也不会中断执行default去跳转执行EXIT,而是在default完成之后,进入条件分支选择使优先进入channel已经ready的case。