go语言notify go语言前景
go语言 不同的接口含有相同的方法 怎么办
下面定义一个结构体类型和该类型的一个方法:
创新互联公司长期为上千客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为宁安企业提供专业的网站建设、网站设计,宁安网站改版等技术服务。拥有十余年丰富建站经验和众多成功案例,为您定制开发。
复制代码代码如下:
type User struct {
Name string
Email string
}
func (u User) Notify() error
首先我们定义了一个叫做 User 的结构体类型,然后定义了一个该类型的方法叫做 Notify,该方法的接受者是一个 User 类型的值。要调用 Notify 方法我们需要一个 User 类型的值或者指针:
复制代码代码如下:
// User 类型的值可以调用接受者是值的方法
damon := User{"AriesDevil", "ariesdevil@xxoo.com"}
damon.Notify()
// User 类型的指针同样可以调用接受者是值的方法
alimon := User{"A-limon", "alimon@ooxx.com"}
alimon.Notify()
Golang实验性功能SetMaxHeap 固定值GC
简单来说, SetMaxHeap 提供了一种可以设置固定触发阈值的 GC (Garbage Collection垃圾回收)方式
官方源码链接
大量临时对象分配导致的 GC 触发频率过高, GC 后实际存活的对象较少,
或者机器内存较充足,希望使用剩余内存,降低 GC 频率的场景
GC 会 STW ( Stop The World ),对于时延敏感场景,在一个周期内连续触发两轮 GC ,那么 STW 和 GC 占用的 CPU 资源都会造成很大的影响, SetMaxHeap 并不一定是完美的,在某些场景下做了些权衡,官方也在进行相关的实验,当前方案仍没有合入主版本。
先看下如果没有 SetMaxHeap ,对于如上所述的场景的解决方案
这里简单说下 GC 的几个值的含义,可通过 GODEBUG=gctrace=1 获得如下数据
这里只关注 128-132-67 MB 135 MB goal ,
分别为 GC开始时内存使用量 - GC标记完成时内存使用量 - GC标记完成时的存活内存量 本轮GC标记完成时的 预期 内存使用量(上一轮 GC 完成时确定)
引用 GC peace设计文档 中的一张图来说明
对应关系如下:
简单说下 GC pacing (信用机制)
GC pacing 有两个目标,
那么当一轮 GC 完成时,如何只根据本轮 GC 存活量去实现这两个小目标呢?
这里实际是根据当前的一些数据或状态去 预估 “未来”,所有会存在些误差
首先确定 gc Goal goal = memstats.heap_marked + memstats.heap_marked*uint64(gcpercent)/100
heap_marked 为本轮 GC 存活量, gcpercent 默认为 100 ,可以通过环境变量 GOGC=100 或者 debug.SetGCPercent(100) 来设置
那么默认情况下 goal = 2 * heap_marked
gc_trigger 是与 goal 相关的一个值( gc_trigger 大约为 goal 的 90% 左右),每轮 GC 标记完成时,会根据 |Ha-Hg| 和实际使用的 cpu 资源 动态调整 gc_trigger 与 goal 的差值
goal 与 gc_trigger 的差值即为,为 GC 期间分配的对象所预留的空间
GC pacing 还会预估下一轮 GC 发生时,需要扫描对象对象的总量,进而换算为下一轮 GC 所需的工作量,进而计算出 mark assist 的值
本轮 GC 触发( gc_trigger ),到本轮的 goal 期间,需要尽力完成 GC mark 标记操作,所以当 GC 期间,某个 goroutine 分配大量内存时,就会被拉去做 mark assist 工作,先进行 GC mark 标记赚取足够的信用值后,才能分配对应大小的对象
根据本轮 GC 存活的内存量( heap_marked )和下一轮 GC 触发的阈值( gc_trigger )计算 sweep assist 的值,本轮 GC 完成,到下一轮 GC 触发( gc_trigger )时,需要尽力完成 sweep 清扫操作
预估下一轮 GC 所需的工作量的方式如下:
继续分析文章开头的问题,如何充分利用剩余内存,降低 GC 频率和 GC 对 CPU 的资源消耗
如上图可以看出, GC 后,存活的对象为 2GB 左右,如果将 gcpercent 设置为 400 ,那么就可以将下一轮 GC 触发阈值提升到 10GB 左右
前面一轮看起来很好,提升了 GC 触发的阈值到 10GB ,但是如果某一轮 GC 后的存活对象到达 2.5GB 的时候,那么下一轮 GC 触发的阈值,将会超过内存阈值,造成 OOM ( Out of Memory ),进而导致程序崩溃。
可以通过 GOGC=off 或者 debug.SetGCPercent(-1) 来关闭 GC
可以通过进程外监控内存使用状态,使用信号触发的方式通知程序,或 ReadMemStats 、或 linkname runtime.heapRetained 等方式进行堆内存使用的监测
可以通过调用 runtime.GC() 或者 debug.FreeOSMemory() 来手动进行 GC 。
这里还需要说几个事情来解释这个方案所存在的问题
通过 GOGC=off 或者 debug.SetGCPercent(-1) 是如何关闭 GC 的?
gc 4 @1.006s 0%: 0.033+5.6+0.024 ms clock, 0.27+4.4/11/25+0.19 ms cpu, 428-428-16 MB, 17592186044415 MB goal, 8 P (forced)
通过 GC trace 可以看出,上面所说的 goal 变成了一个很诡异的值 17592186044415
实际上关闭 GC 后, Go 会将 goal 设置为一个极大值 ^uint64(0) ,那么对应的 GC 触发阈值也被调成了一个极大值,这种处理方式看起来也没什么问题,将阈值调大,预期永远不会再触发 GC
那么如果在关闭 GC 的情况下,手动调用 runtime.GC() 会导致什么呢?
由于 goal 和 gc_trigger 被设置成了极大值, mark assist 和 sweep assist 也会按照这个错误的值去计算,导致工作量预估错误,这一点可以从 trace 中进行证明
可以看到很诡异的 trace 图,这里不做深究,该方案与 GC pacing 信用机制不兼容
记住,不要在关闭 GC 的情况下手动触发 GC ,至少在当前 Go1.14 版本中仍存在这个问题
SetMaxHeap 的实现原理,简单来说是强行控制了 goal 的值
注: SetMaxHeap ,本质上是一个软限制,并不能解决 极端场景 下的 OOM ,可以配合内存监控和 debug.FreeOSMemory() 使用
SetMaxHeap 控制的是堆内存大小, Go 中除了堆内存还分配了如下内存,所以实际使用过程中,与实际硬件内存阈值之间需要留有一部分余量。
对于文章开始所述问题,使用 SetMaxHeap 后,预期的 GC 过程大概是这个样子
简单用法1
该方法简单粗暴,直接将 goal 设置为了固定值
注:通过上文所讲,触发 GC 实际上是 gc_trigger ,所以当阈值设置为 12GB 时,会提前一点触发 GC ,这里为了描述方便,近似认为 gc_trigger=goal
简单用法2
当不关闭 GC 时, SetMaxHeap 的逻辑是, goal 仍按照 gcpercent 进行计算,当 goal 小于 SetMaxHeap 阈值时不进行处理;当 goal 大于 SetMaxHeap 阈值时,将 goal 限制为 SetMaxHeap 阈值
注:通过上文所讲,触发 GC 实际上是 gc_trigger ,所以当阈值设置为 12GB 时,会提前一点触发 GC ,这里为了描述方便,近似认为 gc_trigger=goal
切换到 go1.14 分支,作者选择了 git checkout go1.14.5
选择官方提供的 cherry-pick 方式(可能需要梯子,文件改动不多,我后面会列出具体改动)
git fetch "" refs/changes/67/227767/3 git cherry-pick FETCH_HEAD
需要重新编译Go源码
注意点:
下面源码中的官方注释说的比较清楚,在一些关键位置加入了中文注释
入参bytes为要设置的阈值
notify 简单理解为 GC 的策略 发生变化时会向 channel 发送通知,后续源码可以看出“策略”具体指哪些内容
返回值为本次设置之前的 MaxHeap 值
$GOROOT/src/runtime/debug/garbage.go
$GOROOT/src/runtime/mgc.go
注:作者尽量用通俗易懂的语言去解释 Go 的一些机制和 SetMaxHeap 功能,可能有些描述与实现细节不完全一致,如有错误还请指出
Go 语言 channel 的阻塞问题
Hello,大家好,又见面了!上一遍我们将 channel 相关基础以及使用场景。这一篇,还需要再次进阶理解channel 阻塞问题。以下创建一个chan类型为int,cap 为3。
channel 内部其实是一个环形buf数据结构 ,是一种滑动窗口机制,当make完后,就分配在 Heap 上。
上面,向 chan 发送一条“hello”数据:
如果 G1 发送数据超过指定cap时,会出现什么情况?
看下面实例:
以上会出现什么,chan 缓冲区允许大小为1,如果再往chan仍数据,满了就会被阻塞,那么是如何实现阻塞的呢?当 chan 满时,会进入 gopark,此时 G1 进入一个 waiting 状态,然后会创建一个 sudog 对象,其实就sendq队列,把 200放进去。等 buf 不满的时候,再唤醒放入buf里面。
通过如下源码,你会更加清晰:
上面,从 chan 获取数据:
Go 语言核心思想:“Do not communicate by sharing memory; instead, share memory by communicating.” 你可以看看这本书名叫:Effective Go
如果接收者,接收一个空对象,也会发生什么情况?
代码示例 :
也会报错如下:
上面,从 chan 取出数据,可是没有数据了。此时,它会把 接收者 G2 阻塞掉,也是和G1发送者一样,也会执行 gopark 将状态改为 waiting,不一样的点就是。
正常情况下,接收者G2作为取出数据是去 buf 读取数据的,但现在,buf 为空了,此时,接收者G2会将sudog导出来,因为现在G2已经被阻塞了嘛,会把G2给G,然后将 t := -ch 中变量 t 是在栈上的地址,放进去 elem ,也就是说,只存它的地址指针在sudog里面。
最后, ch - 200 当G1往 chan 添加200这个数据,正常情况是将数据添加到buf里面,然后唤醒 G2 是吧,而现在是将 G1 的添加200数据直接干到刚才G2阻塞的t这里变量里面。
你会认为,这样真的可以吗?想一想,G2 本来就是已经阻塞了,然后我们直接这么干肯定没有什么毛病,而且效率提高了,不需要再次放入buf再取出,这个过程也是需要时间。不然,不得往chan添加数据需要加锁、拷贝、解锁一序列操作,那肯定就慢了,我想Go语言是为了高效及内存使用率的考虑这样设计的。(注意,一般都是在runtime里面完成,不然会出现象安全问题。)
总结 :
chan 类型的特点:chan 如果为空,receiver 接收数据的时候就会阻塞等待,直到 chan 被关闭或者有新的数据到来。有这种个机制,就可以实现 wait/notify 的设计模式。
相关面试题:
Go语言设计与实现(上)
基本设计思路:
类型转换、类型断言、动态派发。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 映射存储组),每个组只进行一次访问,组内每个协程会获得对应结果的一个拷贝。
结构:
逻辑:
细节:
部件:
如有错误,请批评指正。
名称栏目:go语言notify go语言前景
本文链接:http://azwzsj.com/article/dogigis.html