跳转至

Goroutine

goroutine是Go语言对于协程的支持,可以把它理解为Go语言的协程。这是一个Go语言并发编程的终极杀器,它让我们的并发编程变得简单。

Go语言的并发只会用到goroutine,并不需要我们去考虑用多进程或者是多线程。有过C++或者Java经验的同学可能知道,线程本身是有一定大小的,一般OS线程栈大小为2MB,且线程在创建和上下文切换的时候是需要消耗资源的,会带来性能损耗,所以在我们用到多线程技术的时候,我们往往会通过池化技术,即创建线程池来管理一定数量的线程。 在Go语言中,一个goroutine栈在其生命周期开始时占用空间很小(一般2KB),并且栈大小可以按需增大和缩小,goroutine的栈大小限制可以达到1GB,但是一般不会用到这么大。所以在Go语言中一次创建成千上万,甚至十万左右的goroutine理论上也是可以的。 在Go语言中,我们用多goroutine来完成并发,在某个任务需要并发执行的时候,只需要把这个任务包装成一个函数,开启一个goroutine去执行这个函数就可以了。并不需要我们来维护一个类似于线程池的东西,也不需要我们去关心协程是怎么切换和调度的,因为这些都已经有Go语言内置的调度器帮我们做了,并且效率还非常高。

Goroutine使用

goroutine使用起来非常方便,通常我们会将需要并发的任务封装成一个函数,然后再该函数前加上go关键字就行了,这样就开启了一个goroutine

Go
func()
go func()  // 会并发执行这个函数

主协程

和其它语言一样,Go程序的入口也是main函数。在程序开始执行的时候,Go程序会为main函数创建一个默认的goroutine,我们称之为主协程,我们后来人为的创建的一些goroutine,都是在这个主goroutine的基础上进行的。 下面请看个例子:

Go
package main

import "fmt"

func myGroutine() {
        fmt.Println("myGroutine")
}

func main() {
        go myGroutine()
        fmt.Println("end!!!")
}
运行结果:
Text Only
end!!!
很奇怪,明明是多协程任务,为什么只打印了主协程里的"end!!!",而没有打印我们开启的协程里的输出"myGroutine",按理不是应该都打印出来吗? 这是因为:当main函数返回的时候该goroutine就结束了,当主协程退出的时候,其他剩余的goroutine不管是否运行完,都会跟着结束。所以,这里主协程打印完"end!!!"之后就退出了,myGroutine协程可能还没运行到fmt.Println("myGroutine")语句也跟着退出了。 接下来我们让主goroutine执行完fmt.Println("end!!!")之后不立刻退出,而是等待2s,看一下运行结果:
Go
package main

import (
 "fmt"
 "time"
 )

func myGroutine() {
        fmt.Println("myGroutine")
}

func main() {
        go myGroutine()
        fmt.Println("end!!!")
        time.Sleep(2*time.Second)
}
运行结果:
Text Only
end!!!
myGroutine
此时打印出了我们想要的结果,这里我们通过让主协程睡眠2s来等待子协程执行完了之后再退出,后面我们会学习到更好的方法,这里就不再过多阐述。

多协程调用

在Go语言中,我们可以通过go关键字来开启多个协程,每个协程可以并发执行,互不干扰。

Go
package main

import (
    "fmt"
    "sync"
    "time"
)

func myGoroutine(name string, wg *sync.WaitGroup) {
    defer wg.Done()

    for i := 0; i < 5; i++ {
       fmt.Printf("myGroutine %s\n", name)
       time.Sleep(10 * time.Millisecond)
    }
}

func main() {
    var wg sync.WaitGroup
    wg.Add(2)

    go myGoroutine("goroutine1", &wg)
    go myGoroutine("goroutine2", &wg)

    wg.Wait()
}
运行结果:
Text Only
myGroutine goroutine1
myGroutine goroutine2
从结果中可以看到,两个协程并发执行,互不干扰。注意在上述例子中,我们使用了sync.WaitGroup来等待所有协程执行完毕之后再退出。关于sync.WaitGroup的详细介绍,可以参考sync.WaitGroup。后续也会在sync章节详细介绍。