Goroutine
说goroutine之前先来说一下协程和线程和进程
| 协程 | 线程 | 进程 |
|---|---|---|
| 用户态,轻量级线程 | 内核态 | 启动程序的启动实例 |
| 比线程更轻量级的微线程,一个线程可以有多个协程 | 线程是操作系统能够进行运算调度的最小单位,线程被包含在进程之中,是进程中的实际运作单位 | 进程是资源分配的基本单位 |
并发与并行
-
并发是指多线程程序在一个核的cpu上运行(许多蚂蚁在一根树枝上爬)
-
并行是多线程程序在多个核的cpu上运行(许多树枝上有许多蚂蚁在爬)
1.什么是goroutine
Go实现了两种并发形式
- 第一种是大家普遍认知的:多线程共享内存。其实就是Java或者C++等语言中的多线程开发。
- 第二种是Go语言特有的,也是Go语言推荐的:CSP并发模型(通过通信来共享内存)。
Goroutine是与其他函数或方法同时运行的函数或方法,可以被认为是轻量级线程,区别于进程,线程,协程。
2.goroutine在线程上的优势
-
goroutine(堆栈大小可以根据应用程序需求改变)比创建线程(堆栈大小固定)成本小
-
goroutine被多路复用到较少的OS线程。一个程序中可能只有一个线程和上千个goroutine。
-
使用goroutine访问共享内存,通过设计的通道可以防止竞态条件发生。通道可以被认为是goroutine通信的管道。
3.如何使用Goroutine
在函数或方法调用前面加上
go关键字
一个goroutine必定对应一个函数
func hello(){
fmt.Println("hello")
}
func main(){
go hello() //启动一个goroutine
fmt.Println("gee")
}
运行结果为
gee
在程序启动时,Go程序就会为main()函数创建一个默认的goroutine。当main()函数返回的时候该goroutine就结束了,所有在main()函数中启动的goroutine也就会一同结束。所以输出结果只是一个gee。
进行修改:
func hello(){
fmt.Println("hello")
}
func main(){
go hello() //启动一个goroutine
time.Sleep(1*time.Second)
fmt.Println("gee")
}
结果为
hello
gee
main goroutine因为time.Sleep()函数等待新的goroutine hello创建并执行后再执行main中的Println,最后整个main goroutine结束。
如果把time.Sleep()函数放置到最后,则输出顺序改变——先执行main() goroutine,输出gee,然后延时,该延时足以让hello goroutine运行,输出hello,最后延时结束,main() goroutine结束。(注意两个goroutine是并发的。)
3.1 启动多个goroutine
举一个例子
func main(){
for i:=0;i<5;i++{
go godsay()
}
time.Sleep(4*time.Second)
}
func godsay(){
time.Sleep(3*time.Second)
fmt.Println("...god...")
}
结果为
...god...
...god...
...god...
...god...
...god...
main函数启动了5个godsaygoroutine,让其休眠3秒后打印。
再进行修改
func main(){
for i:=0;i<5;i++{
go sleepy(i)
}
time.Sleep(4*time.Second)
}
func sleepy(id int){
time.Sleep(3*time.Second)
fmt.Println("...god...",id,"stepback")
}
结果为
...god... 3 stepback
...god... 2 stepback
...god... 4 stepback
...god... 0 stepback
...god... 1 stepback
可以看到,输出顺序是随机的,这是因为goroutine是并发执行的,而goroutine的调度是随机的。
再运行程序的时候可以明显感受到等待时间并不按照预期,我们通过Go通道来控制goroutine结束时间。