GO语言学习笔记-Goroutines

原文,建议理解并发(concurrency)、并行(parallelism)区别后再看这方面的内容。

Goroutines是啥?

Goroutines是一个可以和其他函数或方法并发执行的函数或方法。也可以把它理解为轻量级的线程(roy注:这话听起来和大python中的协程很像啊!),而创建Goroutine的开销却远远小于线程。因此在大多数的Go程序都可以并发执行成千上万的Goroutine。

Goroutines的优势

  • Goroutine和线程相比及其节省开销,它们仅需要占用几kb的栈空间,而且栈空间可以根据程序的需要增加或回收。而同样情形下线程占用的栈空间只能被指定并且不可修改。
  • Goroutine采用多路复用的方式来减少对系统线程的占用。程序中一个线程中可能包含几千个Goroutine,如果任何Goroutine在线程中阻塞比如需要等待用户输入,那么将会创建新的系统线程并把剩下的Goroutine转移到新系统线程中。所有的操作都由运行时自动处理,作为开发人员不用纠结于实现这个的细节了,Go为此提供了清晰的API。
  • Goroutine之间的通信使用channel,channel被设计用来防止Goroutine之间访问共享内存可能造成的冲突。channel可以理解为Goroutine通信的管道。我们将在后面的文章中讨论channel。

如何启动一个Goroutine

在调用函数或者方法时前面加上go你将启动一个新的Goroutine。

package main

import (  
    "fmt"
)

func hello() {  
    fmt.Println("Hello world goroutine")
}
func main() {  
    go hello()
    fmt.Println("main function")
}

在11行,go hello()启动了一个新的Goroutine,现在hello()函数将和main()函数并发执行,main()函数在其拥有的被称为main Goroutine的Goroutine中执行。

运行上面代码,你将发现只有main function被输出了,搞毛线?我们需要理解Goroutine的2个主要的属性来解释为什么会这样。

  • 当一个新Goroutine开始执行时将会立刻返回。和函数不同,程序并不会等待Goroutine执行结束,而是立刻执行下一行代码并且忽略Goroutine的返回值。
  • 如果main Goroutine结束,那么程序就结束了而且所有的Goroutine都不会运行。

我猜你已经明白为什么我们的Goroutine没有执行了。11行程序立刻执行了下一行输出了main function而不是等待Goroutine执行结束。main Goroutine执行结束后其他代码没有机会执行,所以helloGoroutine没机会执行。

现在我们修复这个问题:

package main

import (  
    "fmt"
    "time"
)

func hello() {  
    fmt.Println("Hello world goroutine")
}
func main() {  
    go hello()
    time.Sleep(1 * time.Second)
    fmt.Println("main function")
}

13行我们调用time包中的Sleep方法,这样main Goroutine被阻塞1秒钟,程序结束前go hello()有足够的时间执行。所以这个程序先输出Hello world goroutine,然后输出main function

main Goroutine中使用Sleep来等待其他Goroutine执行完毕仅仅是用来方便理解Goroutine如何工作,Channel才是用来阻塞main Goroutine等待其他Goroutine执行完毕的主要方式。我们将在下一篇文章解释。

启动多个Goroutine

让我们写个启动多个Goroutine的程序来更好的理解:

package main

import (  
    "fmt"
    "time"
)

func numbers() {  
    for i := 1; i <= 5; i++ {
        time.Sleep(250 * time.Millisecond)
        fmt.Printf("%d ", i)
    }
}
func alphabets() {  
    for i := 'a'; i <= 'e'; i++ {
        time.Sleep(400 * time.Millisecond)
        fmt.Printf("%c ", i)
    }
}
func main() {  
    go numbers()
    go alphabets()
    time.Sleep(3000 * time.Millisecond)
    fmt.Println("main terminated")
}

上面的程序启动了2个Goroutine,这2个Goroutine并发执行。numberssleep 250毫秒后输出1然后再次sleep输出2,直到循环结束输出5。同样的alphabets函数输出ae,每次sleep 400毫秒。main Goroutine启动上面2个Goroutine后sleep 3000毫秒并结束。

程序输出如下:

1 a 2 3 b 4 c 5 d e main terminated


原文最后还有个图解释输出为啥是上面那样,这里roy就不翻译了,不明白的可以自己去原文中查看。