[Golang] goroutine
文章目录
- [Golang] goroutine
- 并发
- 进程和线程
- 协程
- goroutine
- 概述
- 如何使用goroutine
并发
进程和线程
谈到并发,大多都离不开进程和线程,什么是进程、什么是线程?
-
进程可以这样理解:进程就是运行着的程序,它是程序在操作系统的一次执行过程,是一个程序的动态概念,进程是操作系统分配资源的基本单位。
-
线程可以这样理解:线程是一个进程的执行实体,它是比进程粒度更小的执行单元,也是真正运行在cpu上的执行单元,线程是CPU调度资源的1基本单位
进程可以包含多个进程,需要记住的是进程和线程,一个是操作系统分配资源的基本单位,一个是操作系统调度的基本单位。
协程
协程可以理解为用户态线程,是更轻量级的线程。区别于线程,协程的调度在用户态进行,不需要切换到内核态,所以不由操作系统参与,由用户自己控制。在一些支持协程的高级语言中,大多都实现了自己的协程调度器,比如golang就有GMP模型、python就有asyncio等等。
- 协程有独立的栈空间,但是共享堆空间
- 一个进程可以跑多个线程,一个线程可以跑多个协程
goroutine
概述
goroutine就是golang对协程的支持,可以把它理解为golang的协程。
golang的并发只会用到goroutine,所以我们并不用去考虑使用进程、线程。一般线程栈本身大小大约是2MB,而且线程在切换上下文时是消耗资源的,会带来性能消耗,所以我们往往在使用多线程技术时,会通过池化技术,即创建线程池来管理一定数量的线程。
但是在golang中,一个goroutine栈在一开始占用的空间很小,一般只有2KB,并且它的栈大小可以按需求变大或者变小,最大时可以达到1GB(但是一般不用这么大)。所以在golang中一次创建成千上万个或10万个协程理论上也是有可能的。
在golang中,我们使用goroutine完成并发,在某个任务需要并发执行时,只需要把这个任务包装成一个函数,去开启一个goroutine去执行这个函数即可。我们不用维护一个线程池类似的东西,也不需要去关心协程是怎么切换和调度的,因为golang已经内置了调度器帮我们做了,并且效率非常高。
如何使用goroutine
func()
go func()//并发执行
和其他语言相同,golang程序的入口就是main函数。在程序开始执行时,Go程序会为main函数创建一个默认的goroutine,我们称为主协程,我们后来人为的创建的一些goroutine,都是在这个主协程上执行的。
比如:
package main
import "fmt"
func myGoroutine() {
fmt.Println("son")
}
func main() {
go myGoroutine()
fmt.Println("father")
}
运行结果:
但是为什么只有主协程在打印,我们创建的协程没有进行打印呢?
这是因为,当main()函数返回时这个goroutine也就是结束了,主协程结束,其他协程不管是不是运行完,都会跟着结束。所以,当主协程打印完“father”返回后,myGoroutine协程还没来的及运行到打印也就是跟着返回了。
所以,我们想看到都打印,只需要让主协程等待几秒就可以了。
package main
import (
"fmt"
"time"
)
func myGoroutine() {
fmt.Println("son")
}
func main() {
go myGoroutine()
fmt.Println("father")
time.Sleep(2 * time.Second)
}
运行结果:
后面还有更好的方法,不用再让主协程睡眠了。
比如:
package main
import (
"fmt"
"sync"
"time"
)
func myGoroutine(name string, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 2; i++ {
fmt.Printf("myGoroutine %s\n", name)
time.Sleep(1 * time.Second)
}
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go myGoroutine("张三", &wg)
go myGoroutine("李四", &wg)
time.Sleep(2 * time.Second)
wg.Wait()
}
运行结果: