Photo by Aral Tasher on Unsplash

Go语言基础 — goroutine

Golang Basic — goroutine

--

下面的代码,在for里面调用函数前加上了go之后,使func进行并发执行。

  • 上面的代码如果不加go执行的话,只会在第一次i=0的时候无限循环执行fmt.Printf("Hello from "+"goroutine %d\n", i)
  • 如果加上go,但是不加time.Sleep(time.Minute)的话,在func执行之前,for文就会结束,导致内部的func来不及执行。

go开启的其实不是线程,而是协程。

  • 轻量级线程
  • 非抢占式多任务处理,由协程主动交出控制权
  • 编译器/解释器/虚拟机层面的多任务(线程是系统级)
  • 多个协程可能在一个或者多个线程上运行

非抢占式

关于非抢占式可以在看一个例子。

这段代码执行以后我们会发现程序死机了。

主要原因有2个

  • a[i]++无法主动交出控制权
  • main函数自身也是一个协程,但是time.Sleep(time.Minute)因为没有人交出控制权,导致main一直等待。

从之前的代码可以看到,io操作时可以自动交出控制权的,如何手动交出控制权?可以使用runtime.Gosched()。一般情况下不会用到runtime.Gosched()

关于闭包

还有一个点,可以看到for的里面的函数func定义了参数i,每次调用的时候都传入了i。理论上我们应该直接用i的。

如果我们去掉传参的处理执行的话发现会报错。

这个时候我们可以用go run -race goroutine.go来看一下发生了什么。

出错的原因是因为如果不把i作为参数传给func,就不会形成闭包,在最后一次执行for的时候i会是10,导致a[10]超出范围。你也可以把var a [10]int改成var a [11]int这样就不会报错了,但是这很不安全,所以不建议这样做。

协程和普通函数

普通函数

doWork执行完把控制权交还给main

协程

main函数和doWrok之间可以互相交换控制权

其他语言的协程支持

go语言的调度器

在go语言里,具体每个协程如何分配到线程里,都是交给调度器来分配,我们不用操心。

goroutine的定义

  • 直接在函数前加入go
  • 不需要再定义时区分是否是异步函数
  • 调度器在合适的点进行切换
  • 使用-race检测数据访问冲突

goroutine的切换点

下面这些点可以参考,但是由于我们无法100%控制,所以只能是参考。

  • I/O select
  • channel
  • 等待锁
  • 函数调用(有时)
  • runtime.Gosched()

--

--