并发与并行
goroutine
go天生支持并发,像java等语言,并发时都是交给操作系统去分配,而在go中,go内置实现了任务的调度和管理。
go中的并发由goroutine实现,类似于线程。创建goroutine只需要在函数前加一个go关键字,当我们需要并发时,只需要把任务写到函数中,然后使用go关键字开启一个goroutine去执行即可。
像之前的例子,main函数就会启动一个goroutine来执行go程序,例如以下示例:
func hello() {
fmt.Println("hello goroutine")
}
func main() {
hello() // hello goroutine
fmt.Println("main goroutine") // main goroutine
}面这个例子属于串行方式,也就是程序是按顺序执行,上面程序只有执行完毕,下面的程序才能执行。
这里给hello函数添加go关键字,给它分配一个单独的goroutine来执行,这时这个程序就会有两个goroutine,一个hello函数的,一个main函数的。
func hello() {
fmt.Println("hello goroutine")
}
func main() {
go hello()
fmt.Println("main goroutine") // main goroutine
}
上面函数的打印结果只有一行main goroutine,并没有hello goroutine,原因在于程序没有串行执行,hello函数和main函数是不同goroutine(线程)执行的,当hello还没有执行完时,main就执行结束了,导致了结果只输出了一行。
可以在main函数最后添加一个time.Sleep,来等待一秒,这时hello函数就执行完了,然后一秒后main函数结束,结果是都有输出,并且可以看到程序打印后有明显的停顿一秒现象。
func main() { go hello() // hello goroutine fmt.Println("main goroutine") // main goroutine time.Sleep(time.Second) }
多个goroutine
上面例子是单个的goroutine,我们停顿了一秒,如果goroutine过多,且我们无法估测多久会执行完,例如以下代码:
func hello(i int) {
fmt.Println("hello goroutine", i)
}
func main() {
for i := 0; i < 10; i++ {
go hello(i)
}
fmt.Println("main goroutine") // main goroutine time.Sleep(time.Second * 2)
}
这里可以使用sync.WaitGroup来解决,它实现了goroutine的同步,示例代码如下。
var wg sync.WaitGroup // 创建一个sync.WaitGroup func hello(i int) { defer wg.Done() // 每执行一次hello函数,wg就减一 fmt.Println("hello goroutine", i) } func main() { for i := 0; i < 10; i++ { wg.Add(1) // 每使用goroutine调用一次hello,wg就加一 go hello(i) } fmt.Println("main goroutine") // main goroutine wg.Wait() }
上面代码执行发现,每次结果输出完毕后,没有停顿,特别流畅的就结束了,而不是之前使用time.Sleep,总是停顿一会。
上面代码执行多次发现,hello输出时顺序并不是按照0、1、2、3...顺序来的,而是无序的,这是因为它们每个都有自己的goroutine,是并发执行,而不是串行。
goroutine初始化栈大小是2kb,不固定,可增大可缩小,最大可达1gb,因为初始化小,所以goroutine可一次创建十万个也没问题。
gomaxprocs
gomaxprocs用来确定使用多少个os线程来执行go代码,默认是机器上所有的cpu核心数。
在go中,可通过runtime.GOMAXPROCS()来设置当前程序并发时占用的cpu核心数。
示例:例如下面示例,将程序的核心数设置为1,然后通过两个goroutine执行a和b函数,打印结果是先执行完一个函数再执行另一个。
将上面runtime.GOMAXPROCS改为2,即使用并行来执行程序,两个核处理两个goroutine,同时进行。
使用runtime.NumCPU可查看电脑上的核心数。
func a() { for i := 0; i < 10; i++ { fmt.Println("A", i) } } func b() { for i := 0; i < 10; i++ { fmt.Println("B", i) } } func main () { runtime.GOMAXPROCS(1) go a() go b() time.Sleep(time.Second) }
总结
go的并发属于该语言的一大优势,在其它语言中os线程都是内核来调用的,goroutine则是由go的runtime自己调度的。
写程序的时候也很简单,直接go关键字即可,不像其它语言,要关注的东西太多。
发表评论 取消回复