golang基础2
1.接口、方法、断言
一个结构体实现某个interface所有方法,则默认实现了这个接口。
go的接口行为相对隐式,不像java那么明显。
关于类型断言
空接口可以实现类型断言。
c.(string)
这种形式即是代表把c类型转换成string的意思。
关于方法
当一个方法对结构体实现,则编译会自动实现方法对该结构体的指针实现。
而用结构体指针实现的方法,不会自动生成结构体实体方法。
关于空接口
新版叫any,其实就是泛型。
底层是eface,有两个字段,type和ptr。当两个字段都是nil,它是nil接口。
关于nil
对于某类型的一个初始空值。pointer,channel,func,interface,map,or slice type。
nil是有类型的。
关于空结构体
统一指向一个空结构体地址。初始化的指针指向。节约了内存。
2.内存对齐
地址被对齐系数(一般是该类型长度,特殊的有string大小是16,对齐系数是8)整除
关于结构体内存对齐
字段偏移量为的倍数。
结构体长度为的倍数
由于结构体的建构顺序是由字段顺序从小到大分配内存,可以通过改变字段顺序来优化结构体内存配置。
结构体对齐系数是其字段最大对齐系数。
结构体内放置空结构体,需要给该空结构体地址补齐字长,避免地址撞车,因为空结构体没有长度。
3.协程
类并发行为不再是cpu在线程之间来回切换,而是每个线程在协程之间来回切换。
关于GPM模型
有现成的说法可以快速参阅,重点看图。
haoxuebing.github.io/go%E8%BF%9B…
关于抢占式调度
4.锁
atomic操作:硬件层面的加锁机制,原子锁。原子可以理解为最小单位。
底层有一个sema锁,是一个记录协程的等待堆树(平衡二叉树)。
为了方便描述,其实可以直接把等待的堆树(信号量堆)称作sema堆。还有goroutine(协程)我们简称g。
关于互斥锁
Mutex:互斥锁。带两个字段:state和sema。
有两种模式,正常模式和饥饿模式。
在正常模式下,如果一个g尝试获取锁,但发现锁已经被其他 g持有,会进行一定次数的自旋(等待释放,约4-6次)。如果还没获得锁,会进入等待队列(sema堆)挂起,等待被唤醒。
如果有一个 g被唤醒后,发现自己多次被其他新来的 g 抢占(即新的 Goroutine 多次在它前面成功获取了锁),这种情况会触发锁进入饥饿模式。进入starving后,新来的g(协程)全部进入sema树而不会抢锁(也就是CAS,compareAndSwap),而原来sema树中排在前面的g会优先获得锁,直到sema树清空就恢复正常模式。
这个sema堆先进先出(FIFO),因此也可以看做队列。
关于读写锁
读比写多场景适用。读操作线程安全。
互斥锁的写锁由Mutex实现,然后有读锁和写锁各有一个自己维持的队列。
总的来说,写的时候有必要停下读写,而读的时候就没事了。
关于waitGroup
等待协程组完成用,比如进入游戏之前我需要把所有加载基础组件的事情完成,然后才能进入游戏。这时候加载基础组件就需要用到waitGroup。那么在加载游戏之前,waitGroup把任务数记录,每完成一个任务进行Done(),也就是count--。在协程后面使用Wait(),等待所有waitGroup的完成。本质上相当于一组协程等待另一组协程(后续的协程),等待队列还是用sema实现。计数归零,则sema中的下一组被唤醒。
另外锁相关的结构都不可拷贝,因为他是掌管线程安全的东西,如果拷贝了就也是线程不安全。永远不要拷贝锁,也不要拷贝带锁结构体。
关于Once
让一个方法只能被协程调用一次。
底层可以用CAS实现,然而多协程去竞争改值容易遇到问题。
用Mutex就可以,让没抢到的人陷入sema休眠。然后CAS,后面直接判断某个flag即可快速跳过。
关于死锁
死锁指相互等待对方释放的锁。
可以用简单代码进行表述:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var mu1, mu2 sync.Mutex
go func() {
mu1.Lock()
fmt.Println("Goroutine 1: locked mu1")
time.Sleep(time.Second)
mu2.Lock() // 在这里会等待 mu2 被释放
fmt.Println("Goroutine 1: locked mu2")
mu2.Unlock()
mu1.Unlock()
}()
go func() {
mu2.Lock()
fmt.Println("Goroutine 2: locked mu2")
time.Sleep(time.Second)
mu1.Lock() // 在这里会等待 mu1 被释放
fmt.Println("Goroutine 2: locked mu1")
mu1.Unlock()
mu2.Unlock()
}()
time.Sleep(3 * time.Second)
}
但现实情况比较复杂,复杂起来可能肉眼一眼出不来。因此我们可以借助一些工具。下面有三个方案:
1.锁拷贝会导致死锁。vet工具可以诊断某文件是否有锁拷贝。
2.race工具可以发现隐含的数据竞争问题。
3.一个死锁检测项目github.com/sasha-s/go-…