Go 语言入门与进阶: 协程 goroutine 实践

1,335 阅读3分钟

这是我参与更文挑战的第 27 天,活动详情查看: 更文挑战

前文回顾

如果你还没有 Go 语言基础,建议阅读我的 从零学 Go

本系列文章,我将会进一步加深对 Go 语言的讲解,更一步介绍 Go 中的包管理、反射和并发等高级特性。

了解完 Go 中基本的线程模型,我们接下来正式进入到 Go 并发编程的实践当中,这其中我们主要介绍 goroutine 和 channel 的使用和特点,并对 sync 包中的部分并发工具进行讲解。

协程 goroutine

协程 goroutine 在 Go 中属于一类轻量级的线程,在运行时由 runtine 管理,我们在以前代码中编写的 main 函数也是运行在 goroutine 之上。启动一个新的 goroutine 的格式如下所示:

go 表达式语句

只要在代码中出现这样一条 go 语句,就表示一个表达式被并发执行。表达式语句可以是内建函数、或者自定义的方法和函数(命名和匿名皆可)。我们通过以下的例子体会一下:

package main

import (
	"fmt"
	"time"
)

func test()  {
	fmt.Println("I am work in a single goroutine")
}

func main()  {
	go test()
	// 主 goroutine 休眠 1 s
	time.Sleep(time.Second)
}

输出的结果预期是:

I am work in a single goroutine

我们首先声明了一个简单的 test 函数,然后让 test 函数在 goroutine 上并发执行。在 go 语句后,我们还通过 time#Sleep 函数让主 goroutine 休息了 1 s 钟。如果我们直接执行 go 语句而不让主 goroutine 休息,如下代码:

package main

import (
	"fmt"
	"time"
)

func test()  {
	fmt.Println("I am work in a single goroutine")
}

func main()  {
	go test()
}

运行后会发现,大多数情况下 test 函数中的打印语句并不会执行。在 Go 中,只要主 goroutine(main函数)结束,就意味着整个程序已经运行结束了。如果 go test() 没能在 main 函数结束之前被调度器调度执行,那么打印语句就没办法执行了。与其他编程语言一样,不同线程内的代码次序并不能说明真正的执行顺序,这需要我们在将来的代码编写中多加关注。

我们可以举一个更复杂的例子来演示:

package main
import (
	"fmt"
)
func setVTo1(v *int)  {

	*v = 1
}
func setVTo2(v *int)  {
	*v = 2
}

func main()  {
	v := new(int)
	go setVTo1(v)
	go setVTo2(v)
	fmt.Println(*v)
}

上述这段代码的结果有可能是多情况,最后的 *v 输出可能是 0、1、2,虽然大多数情况是0。这取决于当时调度器的调度情况,对此,我们不能对并发执行的顺序做过多的假设,不然会造成不可预料的 bug。

go 语句除了启动命名函数,还是启动匿名还是,代码如下所示:

package main

import (
	"fmt"
	"time"
)
func main()  {

	go func(name string) {
		fmt.Println("Hello " + name )
	}("xuan")
	// 主 goroutine 休眠 1 s
	time.Sleep(time.Second)
}

上述代码中,我们通过 go 语句启动了一个匿名函数的并发线程,为了保证结果的输出,我们让主 goroutine 休眠了 1 s。

小结

本文主要介绍了协程 goroutine 的应用实践。Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。通过本文的讲解,大家了解了 goroutine 的基本用法, 后面可以多实践来加深对 goroutine 的认识。

阅读最新文章,关注公众号:aoho求索