goroutine
goroutine(协程)是go语言中独有的一种用于并发编程的机制,在Java或C++中,如果需要实现并发编程,通常需要我们自己维护一个线程池,然后将需要并发的内容包装成一个个的任务放到线程池中去执行,但是在go语言中就不一样,goroutine是由go的运行时调度和管理的。go程序会智能地将 goroutine 中的任务合理地分配给每个CPU去执行,不需要编程人员操心任务调度等问题。
使用goroutine
当某个任务需要并发执行时,我们只需要将并发执行的任务包装成一个函数,开启一个goroutine去执行该函数即可,开启goroutine就是在函数调用前加上go
关键字。
代码示例:
package main
import "fmt"
func main() {
testGoroutine()
fmt.Println("test main...")
}
func testGoroutine() {
fmt.Println("test goroutine...")
}
上述代码是没有开启goroutine进行并发的代码,所以在代码运行时,testGoroutine
方法和main
方法串行执行,依次打印test goroutine...和test main...
。
接下来在testGoroutine函数调用之前加上关键字go
。
go testGoroutine()
运行结果:
在testGoroutine
函数调用前加上go
关键字后反而只打印出了test main...
,这是因为程序在启动时首先会为main函数创建一个默认的goroutine,当main函数执行完的时候该goroutine也就结束了,所以在main函数中启动的goroutine也就会随着main函数结束而一同结束。
要解决这个只需要在main函数中加上time.Sleep
,让main函数等一会,给testGoroutine充分的时间执行即可。
func main() {
go testGoroutine()
fmt.Println("test main...")
time.Sleep(time.Second)
}
运行结果:
这样就会先打印出test main...
,然后再打印test goroutine...
,因为通过关键字go启动testGoroutine函数时,创建goroutine会花费一定的时间,但是main是在已经创建好的goroutine中直接执行的,所以就会先打印出test main...
。
使用go语言并发编程就是这么简单,同时还可以启动多个goroutine。
代码示例:
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 10; i++ {
go testGoroutine(i)
}
fmt.Println("test main...")
time.Sleep(time.Second)
}
func testGoroutine(i int) {
fmt.Println("test goroutine...", i)
}
代码通过 for 循环启动10个 goroutine ,分别在每个 goroutine 中都打印传入的变量 i 。
运行结果:
每一次打印的数字的顺序都应该是不一样的,因为一共启动了10个 goroutine ,这10个 goroutine 是并发执行的,而且 goroutine 是随机调度的。
go 关键字除了使用在调用函数之前,还可以将 go 关键字和函数的声明写到一起。
代码示例:
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 10; i++ {
go func(i int) {
fmt.Println("test goroutine...", i)
}(i)
}
fmt.Println("test main...")
time.Sleep(time.Second)
}
使用该写法同样可以正常启动10个goroutine并发,代码正常运行。
runtime包
runtime 包是 go 语言并发编程中很重要的一个包,这个包里面包含了很多系统资源、并发操作的函数。
系统资源获取
-
runtime.GOOS:获取操作系统类型
-
runtime.GOARCH:获取操作系统内核
-
runtime.NumCPU():获取操作系统核心数
-
runtime.NumGoroutine():获取当前正在执行和排队的任务总数
代码示例:
func main() {
fmt.Println("操作系统:",runtime.GOOS)
fmt.Println("操作系统内核:",runtime.GOARCH)
fmt.Println("操作系统核心数:",runtime.NumCPU())
fmt.Println("任务总数:",runtime.NumGoroutine())
}
运行结果:
runtime.Gosched()
runtime.Gosched()意味着当前goroutine让出CPU,供其它的goroutine
获得执行的机会。同时,当前的goroutine
也会在未来的某个时间点继续运行,假设有一下代码:
func main() {
go func() {
for i := 0; i < 2; i++ {
fmt.Println("test")
}
}()
fmt.Println("main")
}
在这个代码中,大部分情况下都是会打印出 main ,然后程序就正常结束了,具体原因上面有说到,可以通过 time.Sleep(time.Second) 来控制 main 函数等待子协程执行完毕,同时我们还可以使用 runtime.Gosched() 让 main 主协程让出CPU,供子协程获得执行的机会。
代码示例:
func main() {
go func() {
for i := 0; i < 2; i++ {
fmt.Println("test")
}
}()
runtime.Gosched() // main 协程让出CPU,供子协程获得执行的机会
fmt.Println("main")
}
运行结果:
当程序运行到 runtime.Gosched() 时 main 会让出CPU,所以就会先打印出两个 test 然后再打印出 main 。
runtime.Goexit()
结束当前goroutine
,其他的goroutine
不受影响,主程序也一样继续运行,但是在结束goroutine时,会先执行该goroutine中的defer语句。
代码示例:
func main() {
// goroutine A
go func() {
defer fmt.Println("defer")
runtime.Goexit()
fmt.Println("goroutine A")
}()
// goroutine B
go func() {
fmt.Println("goroutine B")
}()
time.Sleep(time.Second)
fmt.Println("main")
}
运行结果:
上述代码所示,一共启动了两个 goroutine ,在 goroutine A 中调用的 runtime.Goexit() ,所以 goroutine A 会退出,但是在退出之前会先执行 defer 语句的内容,结束语句之后的打印语句就不会再执行。虽然 goroutine A 退出了,但是 goroutine B 和 main 主协程都不会受到影响。
runtime.GOMAXPROCS
runtime.GOMAXPROCS 指定使用多少个操作系统线程来执行当前的 go 语言程序,在 go1.5 之前默认使用的单核执行,在 go1.5 之后,默认使用的当前系统的 CPU 核心数,例如当前系统是 8 核心,则就会将代码同时调度到 8 个操作系统线程上执行。
使用一个线程:
func main() {
runtime.GOMAXPROCS(1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println("线程1:", i)
}
}()
go func() {
for i := 0; i < 10; i++ {
fmt.Println("线程2:", i)
}
}()
time.Sleep(time.Second)
}
当把 GOMAXPROCS 设置成 1 时,在代码中同时启动两个 goroutine ,则系统会先将一个任务执行完成之后才会执行第二个任务,所以当前程序打印的结果中,永远都是一个线程从 0 打印到 9,然后再才打印另外一个线程。(无论尝试多少次都是这样)
运行结果:
使用默认线程数( go 版本为 1.17 所以默认为操作系统核心数)
func main() {
go func() {
for i := 0; i < 10; i++ {
fmt.Println("线程1:", i)
}
}()
go func() {
for i := 0; i < 10; i++ {
fmt.Println("线程2:", i)
}
}()
time.Sleep(time.Second)
}
由于没有设置 GOMAXPROCS ,所以使用了默认的线程数,也就是当前系统的核心数,这样就会将代码调度到多个操作系统线程上执行,和上面不一样的是如果尝试执行多次,就会出现线程1和线程2交替打印的情况。
运行结果:
原文始发于微信公众号(良猿):Java转Go—14并发编程
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/215808.html