Go 并发编程-goroutine 初体验

说到 Go 语言,被人讨论最多的就是 Go 很擅长做高并发,并且不需要依赖外部的库,语言本身就支持高并发。

Go 中实现这一能力的秘密是 goroutine,也经常被称之为协程,goroutine 是 Go 对协程的实现。在这篇文章中,会介绍协程的基本概念,以及 goroutine 的基本使用。

1.什么是协程

协程(Coroutine),又被称之为微线程,这个概念出现的时间很早,在 1963 年就有相关的文献发表,但协程真正被用起来的时间很短。

对于操作系统来说,线程是最小的调度单位,但对于一些高并发的环境,线程处理起来就比较吃力,一方面操作系统能够分配的线程数量有限,另外线程之间的切换相对来说也比较大。

所以对于 Java 这类以线程为调度单位的语言,一般会依靠外部的类库来做到高并发,比如 Java 的Netty 就是一个开始高并发应用必不可少的库。

协程和线程非常类似,只是比线程更加轻量级,具体表现在协程之间的切换不需要涉及系统调用,也不需要互斥锁或者信号量等同步手段,甚至都不需要操作系统的支持。

协程与线程的行为基本一致,但是协程是在语言层面实现的,而线程是操作系统实现的。

2. Go 语言的协程

在 Go 语言中,支持两种并发编程的模式,一种就是以 goroutine 和 channel 为主,这种方式称之为 CSP 模式,这种方式的核心是在 goroutine 之间传递值来来实现并发。

还有一种方式是传统的共享内存式的模式,通过一些同步机制,比如锁之类的机制来实现并发。

Go 程序通过 main 函数来启动,main 函数启动的时候也会启动一个 goroutine,称之为主 goroutine。然后在主 goroutine 中通过 go 关键字创建新的 goroutine。go 语句是立马返回的,不会阻塞当前的 goroutine。

一个 Go 程序中可以创建的 goroutine 数量可以比线程数量多很多,这也是 Go 程序可以做到高并发的原因,goroutine 的实现原理,我们后续的文章再详细聊,下面来看看看 goroutine 的使用。

3. goroutine 的基本使用

goroutine 的使用很简单,只需要在调用的函数前面添加 go 关键字,就会创建一个新的 goroutine:

func goroutine1()  {
    fmt.Println("Hello goroutine")
}

func main() {
    go goroutine1()
    fmt.Println("Hello main")
}

但运行上面的代码之后,输出的结果为:

Hello main

预想中的 Hello goroutine 并没有出现,因为 main 方法执行完成之后,main 方法 所在的 goroutine 就销毁了,其他的 goroutine 都没有机会执行完。

可以通过设置一个休眠时间来阻止主 goroutine 执行完成。

func goroutine1()  {
    fmt.Println("Hello goroutine")
}

func main() {
    go goroutine1()
    time.Sleep(1 * time.Second)
    fmt.Println("Hello main")
}

这样,输出结果就和我们预想的一样了:

Hello goroutine
Hello main

但是这种方法也存在一些问题,这个休眠时间不太好设置,设置的过长,会浪费时间,设置的过短, goroutine 还没运行完成,所以最好的方式是让 goroutine 自己来决定。我们再改动一下代码:

func goroutine2(isDone chan bool) {
    fmt.Println("child goroutine begin...")
    time.Sleep(2 * time.Second)
    fmt.Println("child goroutine end...")
    isDone <- true
}

func main() {
    isDone := make(chan bool)
    go goroutine2(isDone)
    <-isDone
    close(isDone)
    fmt.Println("main goroutine end..")
}

在上面的代码中,我们使用了 chan 类型,这个类型我们后续会详细讲解,暂时只需要知道创建一个 chan 类型的变量,传入到一个子 goroutine 之后,它就会阻塞当前的 goroutine,直到子 goroutine 执行完成。这种方式比上面设置休眠时间的方式要优雅很多,也不会产生一些意料之外的结果。

结果输出为:

child goroutine begin...
child goroutine end...
main goroutine end..

但这种方式还是不完美,现在只启动了一个 goroutine,如果要启动多个 goroutine,这种方式就不管用了。当然,肯定还是有解决办法的,看下面的代码:

func goroutine3(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("child goroutine %d begin...\n", id)
    time.Sleep(time.Second)
    fmt.Printf("child goroutine %d end...\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go goroutine3(i, &wg)
    }
    wg.Wait()
}

这个代码看起来要复杂不少,其中 sync 包中包括了 Go 语言并发编程的所有工具,我们用到的 WaitGroup 就是其中的一个工具。

首先创建一个 WaitGroup 类型的变量 wg,每创建一个 goroutine,就向 wg 中加 1,每个 goroutine 执行完成之后,就调用 wg.Done,这样 wg 就会减 1,wg.Wait() 会阻塞当前 goroutine,直到 wg 中的值清零。

如果熟悉其他语言同步机制的人就会想到,这不就是信号量么,是的,这就是使用信号量来实现的。这个 WaitGroup 与 Java 语言中的 CountDownLatch 功能是一样的。

输出的结果也很漂亮:

child goroutine 4 begin...
child goroutine 0 begin...
child goroutine 3 begin...
child goroutine 2 begin...
child goroutine 1 begin...
child goroutine 1 end...
child goroutine 2 end...
child goroutine 3 end...
child goroutine 4 end...
child goroutine 0 end...

到这里,我们了解了 goroutine 的基本使用,但很多情况下,goroutine 不是独立运行的,而经常需要与其他的 goroutine 通信,在下一篇文章中,我们将详细的聊一聊 goroutine 之间的通信方式。

4. 小结

在这篇文章中,我们了解了协程的概念,并且知道了 goroutine 是 Go 语言对协程的实现。也知道了如何通过启动一个新的 goroutine 并发的去做一些事情,同时也知道了如何让 main goroutine 来等待其他 goroutine 工作完成再退出的几种方法。

文 / Rayjun
本文首发于微信公众号【Rayjun】

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

推荐阅读更多精彩内容