优雅地实现协程同步
实际使用:在开启n个协程的时候Add(n)
在每个协程结束都defer Done()
在主协程使用Wait()
阻塞等待子协程结束
Go中的互斥锁和原子操作
互斥锁(Golang实现
sync.Mutex
和sync.RWMutex
)原子操作(Golang实现
sync/atomic
)(补充:原子操作和互斥锁)
Go限流器
官方的基于令牌桶的限流器,见time/rate
包的Limiter
类型
Go协程通信
Go提倡通过通信来共享内存,而不是通过共享内存来通信
前者就是channel
尽管不提倡后者,但go也支持,即通过
sync.Mutex
来实现对临界区的并发安全保证
Go中的依赖管理
GOPATH→Go Vendor→Go Module
GOPATH
GOPATH是Go语言支持的一个环境变量,其value是Go项目的工作区
弊端:无法实现package的多版本控制
Go Vendor
Vendor 是当前项目中的一个目录,其中存放了当前项目依赖的副本(等于是给每个项目添加了其依赖的副本)
弊端:
Go Module
新建项目后
go mod init projectName
go mod tidy
VSCode很难优雅地对Golang项目进行依赖管理,还得是Goland
Go框架三件套
gorm
官方文档【具体使用看文档吧,下面的笔记感觉会有问题】
ORM框架
对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。
ORM框架是连接数据库的桥梁
db.First()
的使用踩坑
使用First 时,需要注意查询不到数据会返回ErrRecordNotFound
。
使用Find查询多条数据,查询不到数据不会返回错误。所以一般使用
db.Find()
,在查询不到数据的时候会返回一个空切片,不会抛出错误使用结构体作为查询条件
当使用结构作为条件查询时,GORM只会查询非零值字段。
这意味着如果您的字段值为0、’’、false 或其他零值,该字段不会被用于构建查询条件,使用Map来构建查询条件
更新时同理,如果要更新为零值,需要使用map;通过select选择字段
{ID: 111}是兜底处理,当使用了
Model
方法,且该对象主键有值,该值会被用于构建条件删除数据
物理删除
db.Delete()
软删除:在结构体中加上
gorm.DeletedAt
字段链式调用时,
Where()
这种api是用来拼SQL的First()
、Find()
、Create()
、Update()
、Delete()
这种api会直接执行SQL,后面再加Where()
不会生效事务
db.Begin()
会返回固化链接,所以要使用tx,而不是db,db是golang维护的链接池Hook
GORM性能提升
- 关闭默认事务
- 缓存预编译语句
- 等等
Kitex
RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP/IP或UDP,为通信程序之间携带信息数据。RPC将原来的本地调用转变为调用远端的服务器上的方法,给系统的处理能力和吞吐量带来了近似于无限制提升的可能。在OSI网络通信模型中,RPC跨域了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
有点晕
Hertz
Hertz是一个 Golang 微服务 HTTP 框架
看文档吧,感觉就是在读文档,但是还没文档讲的细
Go自动内存管理
自动内存管理是对于动态内存而言的
如程序在运行时根据需求动态分配的内存:
malloc()
自动内存管理(垃圾回收):由程序语言的运行时系统管理动态内存
避免手动内存管理,专注于实现业务逻辑
保证内存使用的正确性和安全性:double-free problem, use-after free problem
三个任务:
- 为新对象分配空间
- 找到存活对象
- 回收死亡对象的内存空间
相关概念
Mutator: 业务线程,分配新对象,修改对象指向关系
Collector: GC线程,找到存活对象(内存标记),回收死亡对象的内存空间(内存回收)
Serial GC: 只有一个collector
Parallel GC: 支持多个collectors 同时回收的GC算法
Concurrent GC: mutator(s)和collector(s)可以同时执行
示意图:
concurrent GC的难点在于,collectors必须要感知到对象指向关系的改变
如下图中的GC过程中新添加的b,也需要被标记
- 评价GC算法
- 安全性(Safety): 不能回收存活的对象 基本要求
- 吞吐率(Throughput): $$1-\frac{GC时间}{程序执行总时间}$$
- 暂停时间(Pause time): stop the world (STW) 业务是否感知
- 内存开销(Space overhead) GC元数据开销
- 追踪垃圾回收(Tracing garbage collection)
- 引用计数(Reference counting)
追踪垃圾回收(Tracing garbage collection)
清理不可达对象时会有三种策略
Copying GC
Mark-Sweep GC
Mark-Compact(标记-压缩) GC
分代GC(Generational GC)场景
分代假说:most objects die young
年轻代采用copying GC,因为其存活时间短,所以使用copying GC不会特别耗时
老年代采用Mark-sweep GC,对象存活时间长,反复复制开销较大
若发现碎片较多,可以采取一次Compact(压缩)
引用计数(Reference counting)
计算该对象被多少个指针“引用”,即被多少个指针指向
优点:
内存管理的操作被平摊到程序执行过程中
内存管理不需要了解runtime的实现细节,如C++的智能指针(smart pointer)
追踪垃圾回收(Tracing garbage collection)需要和runtime耦合的更紧密
缺点:
维护引用计数的开销较大: 通过原子操作保证对引用计数操作的原子性和可见性
无法回收环形数据结构,如下图中已经不可达的红色结点组成的环
通过weak reference解决这个问题
内存开销: 每个对象都引入的额外内存空间存储引用数目
回收内存时依然可能引发暂停,如下图中橙色圈出的节点释放后,会引起很多内存的回收,这个过程中仍然可能引发暂停
Go内存分配
分块
缓存
优化原因:
- 分配路径过长
- 小对象过多
优化案例:Balanced GC(指针碰撞风格对小对象进行分配)
Go编译器优化
函数内联
Beast Mode
逃逸分析:分析代码中指针的动态作用域,即指针在何处可以被访问
函数内联可以减少逃逸对象的数量
- 本文链接:https://wan-nan.github.io/2023/01/16/%E9%9D%92%E8%AE%AD%E8%90%A5Golang%E5%AD%A6%E4%B9%A0/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。