一开始你只有一个主协程,如果子协程不启动,即便主协程让出了时间片,也没有可运行的子协程啊。交换顺序就是起到了先启动子协程的作用。
成都创新互联公司是一家以成都网站建设、网页设计、品牌设计、软件运维、seo优化、小程序App开发等移动开发为一体互联网公司。已累计为成都石牌坊等众行业中小客户提供优质的互联网建站和软件开发服务。
go func是golang的协程,就像多线程,异步执行,所以,代码段1执行完3遍后,可能3次协成刚执行完。在代码段1中如果sleep一下应该就能给协程时间执行了。
基本设计思路:
类型转换、类型断言、动态派发。iface,eface。
反射对象具有的方法:
编译优化:
内部实现:
实现 Context 接口有以下几个类型(空实现就忽略了):
互斥锁的控制逻辑:
设计思路:
(以上为写被读阻塞,下面是读被写阻塞)
总结,读写锁的设计还是非常巧妙的:
设计思路:
WaitGroup 有三个暴露的函数:
部件:
设计思路:
结构:
Once 只暴露了一个方法:
实现:
三个关键点:
细节:
让多协程任务的开始执行时间可控(按顺序或归一)。(Context 是控制结束时间)
设计思路: 通过一个锁和内置的 notifyList 队列实现,Wait() 会生成票据,并将等待协程信息加入链表中,等待控制协程中发送信号通知一个(Signal())或所有(Boardcast())等待者(内部实现是通过票据通知的)来控制协程解除阻塞。
暴露四个函数:
实现细节:
部件:
包: golang.org/x/sync/errgroup
作用:开启 func() error 函数签名的协程,在同 Group 下协程并发执行过程并收集首次 err 错误。通过 Context 的传入,还可以控制在首次 err 出现时就终止组内各协程。
设计思路:
结构:
暴露的方法:
实现细节:
注意问题:
包: "golang.org/x/sync/semaphore"
作用:排队借资源(如钱,有借有还)的一种场景。此包相当于对底层信号量的一种暴露。
设计思路:有一定数量的资源 Weight,每一个 waiter 携带一个 channel 和要借的数量 n。通过队列排队执行借贷。
结构:
暴露方法:
细节:
部件:
细节:
包: "golang.org/x/sync/singleflight"
作用:防击穿。瞬时的相同请求只调用一次,response 被所有相同请求共享。
设计思路:按请求的 key 分组(一个 *call 是一个组,用 map 映射存储组),每个组只进行一次访问,组内每个协程会获得对应结果的一个拷贝。
结构:
逻辑:
细节:
部件:
如有错误,请批评指正。
在 Golang 游戏leaf系列(一) 概述与示例 (下文简称系列一)中,提到过Go模块用于创建能够被 Leaf 管理的 goroutine。Go模块是对golang中go提供一些额外功能。Go提供回调功能,LinearContext提供顺序调用功能。善用 goroutine 能够充分利用多核资源,Leaf 提供的 Go 机制解决了原生 goroutine 存在的一些问题:
我们来看一个例子(可以在 LeafServer 的模块的 OnInit 方法中测试):
这里的 Go 方法接收 2 个函数作为参数,第一个函数会被放置在一个新创建的 goroutine 中执行,在其执行完成之后,第二个函数会在当前 goroutine 中被执行。由此,我们可以看到变量 res 同一时刻总是只被一个 goroutine 访问,这就避免了同步机制的使用。Go 的设计使得 CPU 得到充分利用,避免操作阻塞当前 goroutine,同时又无需为共享资源同步而忧心。
这里主动调用了 d.Cb(-d.ChanCb) ,把这个回调取出来了。实际上,在skeleton.Run里会自己取这个通道
看一下源码:
New方法,会生成指定缓冲长度的ChanCb。然后调用Go方法就是先执行第一个func,然后把第二个放到Cb里。现在手动造一个例子:
这里解释一下,d.Go根据源码来看,实际也是调用了一个协程。然后上面两次d.Go并不能保证先后顺序。目前的输出结果是1+2那个先执行了,把3写入d.ChanCb,然后把3读出来,继续读时,d.ChanCb里没有东西,阻塞了。然后1+1那个协程启动了,最后又读到了2。
现在把time.Sleep(time.Second)的注释解开,会是啥结果呢
这里执行到time.Sleep睡着了,上面两个d.Go仍然是不确定顺序的,但是会各自的function先执行掉,然后陆续把cb写入d.ChanCb。看这次输出,1+2先写进去的。所以最后执行d.Cb时,就把3先读出来了。然后d.ChanCb的长度为1,说明还有一个,就是输出2了。
另外,就是close时会判断g.pendingGo
这个例子的意思很明显,NewLinearContext这种方式,即使先调用的慢了半秒,它还是会先执行完。
这里先是用了一个list,加入的时候用mutexLinearGo锁了,都加到最后。然后新开协程去处理,读的时候从最前面开始读,也要用mutexLinearGo锁。执行的时候,也要上锁mutexExecution,确保f()执行完并且写入g.ChanCb回调,这个mutexExecution锁才会解除。现在可以改造一个带回调的例子:
结果说明,确实是2先被写入了d.ChanCb。