以下内容大部分引入:Go语言101,可自行观看。(微信公众号好像打不开超链接)
协程
Go不支持创建系统线程,所以协程是一个Go程序内部唯一的并发实现方式。
协程有时也被称为绿色线程。绿色线程是由程序的运行时(runtime)维护的线程。一个绿色线程的内存开销和情景转换(context switching)时耗比一个系统线程常常小得多。 只要内存充足,一个程序可以轻松支持上万个并发协程。
每个Go程序启动的时候只有一个对用户可见的协程,我们称之为主协程。 一个协程可以开启更多其它新的协程。在Go中,开启一个新的协程是非常简单的。 我们只需在一个函数调用之前使用一个go关键字,即可让此函数调用运行在一个新的协程之中。
并发同步
如果程序执行时没有同步会非常容易出现幻读,脏写等问题。这里直接引用书中的例子:
package main
import (
"log"
"math/rand"
"time"
"sync"
)
var wg sync.WaitGroup
func SayGreetings(greeting string, times int) {
for i := 0; i < times; i++ {
log.Println(greeting)
d := time.Second * time.Duration(rand.Intn(5)) / 2
time.Sleep(d)
}
wg.Done() // 通知当前任务已经完成。
}
func main() {
rand.Seed(time.Now().UnixNano()) // Go 1.20之前需要
log.SetFlags(0)
wg.Add(2) // 注册两个新任务。
go SayGreetings("hi!", 10)
go SayGreetings("hello!", 10)
wg.Wait() // 阻塞在这里,直到所有任务都已完成。
}
一个协程创建成功后会自动进入运行状态,协程只能用运行状态退出,不能从阻塞状态退出。如果协程一直在阻塞状态一般称之为死锁,当程序死锁后会报错。
延迟函数调用
在Go中,一个函数调用可以跟在一个defer关键字后面,成为一个延迟函数调用。 此defer关键字和此延迟函数调用一起形成一个延迟调用语句。当一个延迟调用语句被执行时,其中的延迟函数调用不会立即被执行,而是被推入由当前协程维护的一个延迟调用队列(一个后进先出队列)。类似Java中的压栈弹栈。
func main() {
defer fmt.Println("9")
fmt.Println("0")
defer fmt.Println("8")
fmt.Println("1")
if false {
defer fmt.Println("not reachable")
}
defer func() {
defer fmt.Println("7")
fmt.Println("3")
defer func() {
fmt.Println("5")
fmt.Println("6")
}()
fmt.Println("4")
}()
fmt.Println("2")
return
}
输出结果为:
另外一个例子:
因为这个延迟函数会在return之后执行,而return并不会直接返回,return本质上就是赋值,他等价于:r = n + n;return r;所以 r 就等于 10,r += n;再返回就会返回15
package main
import "fmt"
func Triple(n int) (r int) {
defer func() {
r += n // 修改返回值
}()
return n + n // <=> r = n + n; return
}
func main() {
fmt.Println(Triple(5)) // 15
}
细节:入栈的时候会把值压入栈,也就是说压栈的时候当时的值是 1 出栈的值也是1。
协程和延迟调用的实参的估值时刻
直接看图:
最终结果为:
这块没弄太懂,个人理解为:第一个函数执行的时候进栈记录的是i的值,所以会输出 2 1 0。第二个函数是函数结束后执行的延迟函数,当时i的值是3,所以执行结果是 3 3 3。
这个代码太妙了!!协程开启的时候 a 的值为 123,x == a,后来程序继续往下走,把 a 重新赋值为 789 ,程序睡眠两秒保证同步,最终结果为123 789。太妙了!!!
今天到这!!!!!