一文搞懂go语言goroutine使用

go语言协程使用

前言

协程的使用及控制

协程崩溃处理

协程超时控制

是否可以无限多开协程

高并发下情况下如何开协程

结尾

go语言协程使用

前言

go语言的真正精髓,莫过于go协程和channel.因此对于goroutine和channel的正确使用,非常重要.是编写高并发程序的重要基础.本文试图将协程的使用方方面面讲清楚.

全文阅读大概需要30分钟.如时间不够,欢迎收藏后阅读.

协程的使用及控制

在go语言中开一个协程非常方便,在需要通过协程来执行的函数时,直接在函数前加go关键字就可以

packagemainimport("fmt")funcA(iint){fmt.Println("我是A")}funcmain(){fmt.Println("我是main")goA(1)fmt.Println("执行完了")}

执行后输出

我是main

执行完了

程序正常执行没有报错,但是没有函数A的输出.

这是因为主协程并不会等待子协程执行完才往下走,执行到go后,语句会继续执行,go后面的函数新开一个协程,各跑各的,所以主协程执行完go语句,就无事可做,就退出了.

那怎么让上面的代码打印出函数A的输出.得让主函数待一会儿协程执行完了再退出,或者让主协程不退出,比如在web程序中,主协程是不退出的.

通过sync. WaitGroup的三个方法 Add()Done()Wait() 来实现协程的控制

通过带buffer的channel来控制

通过sync. Cond

下面演示下等待协程完成的代码

packagemainimport("fmt""sync")funcA(iint){fmt.Println("我是A",i)}funcmain(){varwg sync.WaitGroupfmt.Println("我是main")wg.Add(1)gofunc(iint){deferwg.Done()A(i)}(1)wg.Wait()fmt.Println("执行完了")}

我是main我是A1执行完了

下面演示通过channel来控制协程的流程

packagemainimport("fmt")funcA(iint){fmt.Println("我是A",i)}funcmain(){ch:=make(chanbool,1)fmt.Println("我是main")gofunc(iint,chpchan<-bool){deferclose(chp)A(i)fmt.Println("finish")chp<-true}(1,ch)fmt.Println("wait")<-chfmt.Println("执行完了")}

我是mainwait我是A1finish执行完

下面演示通过sync. Cond来实现

packagemainimport("fmt""sync")funcA(iint){fmt.Println("我是A",i)}funcmain(){varlocker=new(sync.Mutex)varcond=sync.NewCond(locker)vardonebool=falsefmt.Println("我是main")cond.L.Lock()gofunc(iint){A(i)fmt.Println("finish")done=truecond.Signal()}(1)fmt.Println("wait")if!done{cond.Wait()cond.L.Unlock()}fmt.Println("执行完了")}

我是mainwait我是A1finish执行完了

代码示例:https://play.studygolang.com/p/y-ushG8zVRP

协程崩溃处理

在go语言中,如果一个协程崩溃了,则所有协程都会退出,比如数组越界,会触发panic(相当于throw exception), 这对持续可运行的应用来说,显然不是我们想要的效果.那这个时候我们需要对崩溃进行修复.在go语言中提供了一个defer和recover来实现崩溃恢复,这个相当于其它语言的try catch的方式.

在使用recover函数时,如果要达到能捕获异常的作用,有几点需要注意:

recover如果想起作用的话, 必须在defered函数前声明,因为只要panic,后面的函数不会被执行

recover函数只有在方法内部发生panic时,返回值才不会为nil,没有panic的情况下返回值为nil

下面用代码示例来说明情况

packagemainimport("fmt""sync")funcA(iint){fmt.Println("我是A",i)panic("崩溃")deferfunc(){//在panic后声明defer,不能捕获异常iferr:=recover();err!=nil{fmt.Println("恢复",err)}}()}funcmain(){varwg sync.WaitGroupfmt.Println("我是main")wg.Add(1)gofunc(iint){deferwg.Done()A(i)}(1)wg.Wait()fmt.Println("执行完了")}

输出:

./prog.go:11:2: unreachable codeGo vet exited.我是main我是A1panic: 崩溃goroutine6[running]:main.A(0x1)/tmp/sandbox516871981/prog.go:10 +0xc5main.main.func1(0xc000018040, 0x1)/tmp/sandbox516871981/prog.go:24 +0x53created by main.main/tmp/sandbox516871981/prog.go:22 +0xd5

再看在panic前声明recover

packagemainimport("fmt""sync")funcA(iint){deferfunc(){//在panic前声明defer,能捕获异常iferr:=recover();err!=nil{fmt.Println("恢复",err)}}()fmt.Println("我是A",i)panic("崩溃")}funcmain(){varwg sync.WaitGroupfmt.Println("我是main")wg.Add(1)gofunc(iint){deferwg.Done()A(i)}(1)wg.Wait()fmt.Println("执行完了")}

输出

我是main我是A1恢复 崩溃执行完了

此时的panic被捕获了

defer recover函数必须放在需要捕获panic的函数前面

因此本示例如果将defer recover放在go func函数中被调用函数f前面,也能捕获住A函数的panic

packagemainimport("fmt""sync")funcA(iint){fmt.Println("我是A",i)panic("崩溃")}funcmain(){varwg sync.WaitGroupfmt.Println("我是main")wg.Add(1)gofunc(iint){deferfunc(){//在调用A函数前声明defer recover,能捕获异常iferr:=recover();err!=nil{fmt.Println("恢复",err)}wg.Done()}()A(i)}(1)wg.Wait()fmt.Println("执行完了")}

输出

我是main我是A1恢复 崩溃执行完了

因此,如果在协程内执行其它函数时,为了保证不崩溃,安全的做法是,提前声明defer recover函数

这样可以保证协程内部崩溃,不会将整个进程崩溃掉

协程超时控制

当你希望控制一个协程的执行时间,如果超过指定时间,还没有执行完,则退出.直接返回超时错误,这个该如何做呢?

通行做法是用select + channel来进行超时控制,

channel发执行完毕的信号,然后超时信号通用ctx. Done()或者time. After(), 或者time. Ticket()来完成超时通知退出,select捕获到其中一个channel有数据,就执行对应的代码,然后退出.

其中且个注意的点是, channel要用有缓冲的,不然,在超时分支退出时,协程还在卡住,造成goroutine泄露.

代码示例如下

packagemainimport("context""fmt""sync""time")funcDo(ctx context.Context,wg*sync.WaitGroup){ctx,cancle:=context.WithTimeout(ctx,time.Second*2)deferfunc(){cancle()wg.Done()}()done:=make(chanstruct{},1)//执行成功的channelgofunc(ctx context.Context){fmt.Println("go goroutine")time.Sleep(time.Second*10)done<-struct{}{}//发送完成的信号}(ctx)select{case<-ctx.Done()://超时fmt.Printf("timeout,err:%v\n",ctx.Err())case<-time.After(3*time.Second)://超时第二种方法fmt.Printf("after 1 sec.")case<-done://程序正常结束fmt.Println("done")}}funcmain(){fmt.Println("main")ctx:=context.Background()varwg sync.WaitGroupwg.Add(1)Do(ctx,&wg)wg.Wait()fmt.Println("finish")}

输出如下:

main

go goroutine

timeout,err:context deadline exceeded

finish

程序执行了超时退出

是否可以无限多开协程

众所周知,协程不同于线程,并不和操作系统的线程有具体的对应关系.协程是由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的使用,有利于更加准确的开发高效的并发处理程序.

全文完,感谢您的阅读.

如有不正确的地方,欢迎留言讨论.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容