一些收藏

Go语言设计与实现

2021-09-13  本文已影响0人  Robin92

编译原理

编译原理

数据结构

数组

内存分配模型

切片

所以将切片+两个通道设计为弹性伸缩的通道队列不会有内存泄漏的问题,弹性通道的设计有以下三步:

  • buf = append(buf, <-inChan) (入队)
  • outCh <- buf[0] 首个被使用(弹出)
  • buf = buf[1:] (更新队列)

但由于扩容会发生拷贝,所以在 buf 量大时,还是会有性能问题。可以设计一个 buf1,一个 buf2,在数据多的时候一个先用于读,另一个只做存,读完第一个后直接交换变量引用。

memmove:是将内存块中的内存拷贝到目标内存区。内置 copy 函数用到了它,虽然说相比于一个个拷贝性能更好,但仍然避免不了大切片拷贝的性能问题。

哈希表

预备内容:

  • 哈希碰撞:哈希函数对不同内容哈希取模后可能映射到相同的索引上,这种问题在哈希表中就叫哈希碰撞。
  • 解决哈希碰撞的一般方法:1. 开放寻址法;2. 拉链法。
  • 装载率:装载率 = 元素数量 / 数组大小 * 100%
  • 哈希的读写性能关键:1. 哈希函数;2. 定位桶的效率;3. 遍历链表。
Map数据结构

字符串

火焰图查看性能损耗时,runtime.slicebytetostringruntime.stringtoslicebyte 就是 byte、string 互相转换的损耗。性能损耗随着长度的增长而增长。

语言基础

函数调用

其实这里没讲闭包的使用。一个函数 A 返回了另一个函数 B(B 函数用了 A 函数中定义的变量),那么 b1 = A(), b2 = A() 时,b1、b2 函数各自拥有 A 中声明的变量。

接口

类型转换、类型断言、动态派发。iface,eface。

反射

接口对象与反射对象

没有 VALUE.(interface{}) 这样类型转换的语句。

反射对象具有的方法:

type Type interface {
  Align() int
  FieldAlign() int 
  Method(int) Method                 // 通过下标返回方法
  MethodByName(string) (Method, int) // 通过String找方法
  NumMethod() int                    // 方法个数
  Implement(u Type) bool             // 判断是否实现了某 reflect.Type 类型
  NumIn() int                        // 返回入参个数
  // ...
}

type Value struct {/* ... */}
func (v Value) Addr() Value
func (v Value) Bool() bool
func (v Value) Bytes() []byte
// ...

常用关键字

for range

select

编译优化:

内部实现:

defer

panic 和 recover

make 和 new

并发编程

Context

type Context interface {
  Deadline() (deadline time.Time, ok bool) // 返回截止时间
  Done() <-chan struct{}                   // 返回关闭的channel指针,用于获取关闭的信息
  Err() error                              // 获取Context结束的原因
  Value(key interface{}) interface{}       // 获取键对应的值
}
// 返回 Context 的几种方法:
func Background() Context {} // 空的接口实现
func TODO() Context {}       // 空的接口实现
// 下面三个的返回值都是一个 ctx 和一个取消函数
func WithCancel(p Context) (Context, CancelFunc) {}                   // 可取消的ctx, cancelCtx
func WithDeadline(p Context, d time.Time) (Context, CancelFunc) {}    // 带超时的cancelCtx
func WithTimeout(p Context, t time.Duration) (Context, CancelFunc) {} // 调用了WithDeadline
// 设置 value 用的,链式存的
func WithValue(p Context, key, val interface{}) Context {} // 可设置值的ctx

实现 Context 接口有以下几个类型(空实现就忽略了):

他们都是通过 Context 字段组成指向父节点的链表。

type valueCtx struct {
    Context
    key, val interface{}
}
type timerCtx struct {
    cancelCtx
    timer *time.Timer // Under cancelCtx.mu.
    deadline time.Time
}
cancelCtx的设计模型

type Mutex struct { // 总共空间 8 字节
    state int32 // 计数+三个状态:饥饿、唤醒、上锁
    sema uint32 // 信号量
}

饥饿模式:
在正常模式下,所有锁的等待者是在一个 FIFO 队列中依次获取锁。但当一个刚被唤起的 goroutine 来争抢锁时,可能会比第一个等待者优先获得,为了减少这种情况的出现,防止 goroutine 被“饿死”,所以有了“饥饿模式”的特殊处理。

Mutex 是个混合锁,如上面所提用到了自旋锁和信号量。在 Go 源码的其他地方也大量用了 Mutex 做基础的构件来实现并发串行控制。

type RWMutex struct {
    w           Mutex  // 读写锁用了读锁做基础
    writerSem   uint32 // 信号量,写申请,读释放
    readerSem   uint32 // 信号量,读申请,写释放
    readerCount int32  // 读者个数
    readerWait  int32  // 写锁在等待读者的个数
}

设计思路:

RWMutex 中内嵌的 Mutex (rw.w)是用来做写与写并发控制的锁;
RWMutex 中不需要对读与读的做并发控制,读会用原子计数方式来计数;
读和写的并发控制是原子计数和信号量(也可以说再加上 Mutex)配合完成的。

以下是读和写并发控制的设计思路,主要靠两个计数和两个信号量相互配合完成。

WaitGroup

Add()Wait()Done()。(其中 Done() 只是 Add(-1))。

用了信号量没用锁。结构:

type WaitGroup struct {
  noCopy noCopy    // 发生值拷贝时分析器会报错
  state1 [3]uint32 // 三个数值:信号量、等待计数、计数
}

设计思路:

部件:

申请信号量就会陷入睡眠。

Once

Do()

部件:

Cond

让多协程任务的开始执行时间归一。(和 Context 相对)

设计思路: 通过一个锁和 FIFO 队列,协程中用 (Wait()) 来等待信号,主控制协程中发送信号通知一个(Signal())或所有(Boardcast())等待者。

部件:

扩展包中的 ErrGroup

作用:并发执行命令,当有一个发生错误时,立即终止所有命令。

设计思路:

部件:

扩展包中的 Semaphore

作用:排队借钱。

设计思路:有一定数量的钱 Weight,每一个 waiter 携带一个 channel 和要借的数量 n。通过队列执行借贷。

部件:

扩展包中的 SingleFlight

作用:防击穿。瞬时的相同请求只调用一次,response 被所有请求共享。

设计思路:用一个 Group 结构处理这一类消息,将请求内容 hash 存入其中一个 map 字段中只进行一次访问,每个 channel 会获得对应的结果。

部件:

上一篇 下一篇

猜你喜欢

热点阅读