一、什么是 Goroutine ?
在java/c++
中我们要实现并发编程的时候,我们通常需要自己维护一个线程池,并且需要自己去包装一个又一个的任务,同时需要自己去调度线程执行任务并维护上下文切换,这一切通常会耗费程序员大量的心智。那么能不能有一种机制,程序员只需要定义很多个任务,让系统去帮助我们把这些任务分配到CPU
上实现并发执行呢?
Go
语言中的goroutine
就是这样一种机制,goroutine
的概念类似于线程,但 goroutine
是由Go的运行时(runtime
)调度和管理的。Go
程序会智能地将 goroutine
中的任务合理地分配给每个CPU
。Go
语言之所以被称为现代化的编程语言,就是因为它在语言层面已经内置了调度和上下文切换的机制。
在Go
语言编程中你不需要去自己写进程、线程、协程,你的技能包里只有一个技能–goroutine
,当你需要让某个任务并发执行的时候,你只需要把这个任务包装成一个函数,开启一个goroutine
去执行这个函数就可以了,就是这么简单粗暴。
二、使用 Goroutine
Go
语言中使用goroutine
非常简单,只需要在调用函数的时候在前面加上go
关键字,就可以为一个函数创建一个goroutine
。
一个goroutine
必定对应一个函数,可以创建多个goroutine
去执行相同的函数。
单个 goroutine
package main import "fmt" func hello() { fmt.Println("Hello Goroutine!") } func main() { go hello() fmt.Println("main goroutine done!") }
执行这段程序:
main goroutine done!
那么问题来了:为什么 Hello Goroutine!
没有输出呢?
在程序启动时,Go
程序就会为main()
函数创建一个默认的goroutine
。
当main()
函数返回的时候该goroutine
就结束了,所以在main()
函数中启动的goroutine
会一同结束,main
函数所在的goroutine
就像是权利的游戏中的夜王,其他的goroutine
都是异鬼,夜王一死它转化的那些异鬼也就全部GG
了。
所以咱们要想办法让main
函数等一等hello
函数,最简单粗暴的方式就是time.Sleep
了。
示例:
package main import "fmt" func hello() { fmt.Println("Hello Goroutine!") } func main() { go hello() fmt.Println("main goroutine done!") time.Sleep(time.Second) // 等待1秒 }
执行这段程序:
Hello Goroutine!
main goroutine done!
多个 goroutine
package main import "fmt" func hello() { fmt.Println("Hello Goroutine!") } func word() { fmt.Println("Word Goroutine!") } func main() { go hello() go word() fmt.Println("main goroutine done!") time.Sleep(2 * time.Second) // 等待2秒 }
执行这段程序:
Hello Goroutine!
Word Goroutine!
main goroutine done!
开启多个 goroutine
只需要再使用一次go
关键字就可以了。
同样的,咱们为了让它能够完整输出,使用 time.Sleep
让main
函数等待 goroutine
执行完成。
sync.WaitGroup 的使用
上面咱们讲了如何开启goroutine
,为了goroutine
正常输出,增加了 time.Sleep
等待。
但在我们实际项目开发中,生硬的使用time.Sleep
肯定是不合适的。那我们应该如何才能正确优雅的让 main
函数等待 goroutine
执行完之后再执行呢?
go
语言提供了一个 sync.WaitGroup
的一个计数器的功能。可以用来优雅的实现 goroutine
的正常执行和 main
函数的等待。
示例:
package main import ( "fmt" "sync" ) var wg sync.WaitGroup func hello(){ defer wg.done() // 计数器 - 1 fmt.Println("Hello Goroutine!") } func main(){ wg.Add(1) // 计数器 + 1 go hello() wg.wait() // 阻塞直到计数器变为0 fmt.Println("main goroutine done!") }
执行这段程序:
Hello Goroutine!
main goroutine done!
方法名 | 功能 |
---|---|
(wg * WaitGroup) Add(delta int) | 计数器+delta |
(wg *WaitGroup) Done() | 计数器-1 |
(wg *WaitGroup) Wait() | 阻塞直到计数器变为0 |
sync.WaitGroup
内部维护着一个计数器,计数器的值可以增加和减少。例如当我们启动了 N
个并发任务时,就将计数器值增加N
。每个任务完成时通过调用Done()
方法将计数器减1。通过调用Wait()
来等待并发任务执行完,当计数器值为0时,表示所有并发任务已经完成。
在实际项目中使用sync.WaitGroup
可以更好的、更优雅的控制goroutine
。
三. 结束语
到此这篇关于Go语言中goroutine的使用的文章就介绍到这了,更多相关Go语言goroutine内容请搜索好代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持好代码网!