|
@@ -705,23 +705,104 @@ for 循环 如果条件表达式部分被省略,则编译器视其为true。
|
|
|
|
|
|
# 协程、延迟函数调用、恐慌和恢复
|
|
|
|
|
|
+## 协程
|
|
|
|
|
|
+![image-20220824175500201](入门.assets/image-20220824175500201.png)
|
|
|
|
|
|
+并行计算属于特殊的并发计算。
|
|
|
|
|
|
+Go 不支持创建系统线程,所以协程是一个Go程序内部唯一的并发实现方式。
|
|
|
|
|
|
+ 我们只需 在一个函数调用之前使用一个go关键字,即可让此函数调用运行在一个新的协程之 中。 当此函数调用退出后,这个新的协程也随之结束了。我们可以称此函数调用为 一个协程调用(或者为此协程的启动调用)。 一个协程调用的所有返回值(如果存 在的话)必须被全部舍弃
|
|
|
|
|
|
+## 并发同步
|
|
|
|
|
|
+并发计算可能会导致数据竞争
|
|
|
|
|
|
+数据竞争,在一个计算向一段内存写数据时,另一个计算在读此段内存数据,结果导 致读出的数据的完整性得不到保证;在一个计算向一段内存写数据的时候,另一个计算也向此段内存写数据,结果导致被写入的数据的完整性得不到保证。
|
|
|
|
|
|
+调用不同的计算,控制他们访问资源的时间段,避免数据竞争发生,称为并发同步(或者数据同步)
|
|
|
|
|
|
+当一个程序的主协程退出后,此程序也就退出了,即使还有一些其它协程在运行。
|
|
|
|
|
|
+ 如何 确保主协程在这20条问候语都打印完毕之后才退出呢?
|
|
|
|
|
|
+我们必须使用某种并发同步 技术来达成这一目标。 Go支持几种并发同步技术(第36章)。 其中, 通道(第21章)是最独特和最常用 的。 但是,为了简单起见,这里我们将使用sync标准库包中的WaitGroup来同步 上面这个程序中的主协程和两个新创建的协程。
|
|
|
|
|
|
+WaitGroup类型有三个方法:Add、Done、Wait
|
|
|
|
|
|
+- ADD 用来注册新的需要完成的任务数
|
|
|
+- Done 用来通知某个任务已经完成了
|
|
|
+- 一个Wait方法调用将阻塞(等待)到所有任务都已经完成之后才继续执行其后的语句
|
|
|
|
|
|
+```go
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "log"
|
|
|
+ "math/rand"
|
|
|
+ "sync"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+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())
|
|
|
+ log.SetFlags(0)
|
|
|
+ wg.Add(2) // 注册两个新任务。
|
|
|
+ go SayGreetings("hi!", 10)
|
|
|
+ go SayGreetings("hello!", 10)
|
|
|
+ wg.Wait() // 阻塞在这里,直到所有任务都已完成。
|
|
|
+ //wg.Wait() 阻塞的是主协程,等两个新协程完成各自任务后,主协程切回运行状态。
|
|
|
+}
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+![image-20220825171232554](入门.assets/image-20220825171232554.png)
|
|
|
+
|
|
|
+**注意,time.Sleep 或者等待系统调用返回的协程被认为是运行状态,而不是阻塞状态。**
|
|
|
+
|
|
|
+协程被创建后,会自动进入运行状态,协程只能从运行状态而不能从阻塞状态退出。
|
|
|
+如果一个运行中的程序当前所有的协程都出 于阻塞状态,则这些协程将永远阻塞下去,程序将被视为死锁了。 当一个程序死锁 后,官方标准编译器的处理是让这个程序崩溃。
|
|
|
+
|
|
|
+## 协程的调度
|
|
|
+
|
|
|
+并非所有处于运行状况的协程都在执行。同一时刻,只能最多有和逻辑CPU一样多的协程在同时执行。runtime.NumCPU 查询当前程序可用的逻辑CPU数目
|
|
|
|
|
|
+重申一下,睡眠和等待系统调用返回子状态被认为是运行状 态,而不是阻塞状态。
|
|
|
|
|
|
+## 延迟函数调用
|
|
|
|
|
|
+一个函数跟在一个defer 关键字后面,成为一个延迟函数调用。
|
|
|
+和协程调用类似,被延迟的函数调用的所有返回值(如果存在)必须全部被舍弃。
|
|
|
|
|
|
+当一个延迟调用语句被执行时,其中的延迟函数调用不会立即被执行,而是被推入 由当前协程维护的一个延迟调用队列。 当一个函数调用返回(此时可能尚未完全退 出)并进入它的退出阶段(第9章)后,所有在执行此函数调用的过程中已经被推入 延迟调用队列的调用将被按照它们被推入的顺序逆序被弹出队列并执行。 当所有这 些延迟调用执行完毕后,此函数调用也就完全退出了。
|
|
|
|
|
|
+**被延迟调用的函数会被推入一个延迟调用队列中,先进后调用;一个函数调用返回并进入它的推出阶段后,会有序按照调用队列顺序进行执行**
|
|
|
+
|
|
|
+## 一个延迟调用可以修改包含此延迟调用的最内层函数的返回值
|
|
|
+
|
|
|
+```go
|
|
|
+func Triple(n int) (r int) {
|
|
|
+ defer func() {
|
|
|
+ fmt.Println(r)
|
|
|
+ r += n // 修改返回值
|
|
|
+ fmt.Println(r)
|
|
|
+ }()
|
|
|
+ return n + n // <=> r = n + n; return
|
|
|
+ // return 10 ——> r=10 ——> 进入defer r=15 ——> return 15
|
|
|
+ //这个函数先执行完return 再执行defer内部,很神奇。
|
|
|
+}
|
|
|
+```
|
|
|
|
|
|
+## 协程和延迟调用的实参的估值时刻
|