一些Go的知识点记录


gmp调度过程

普通进程调度可以使所有进程看起来都在运行,但是进程的创建、切换、销毁都会占时间,CPU有很大一部分都是用来进程调度。

线程调度方法

先到先得、短作业优先。

这两个的问题:紧急任务无法处理、后面的小任务会被当前执行的大任务耽误。

给线程划分优先级、CPU执行分时间片,时间片时间内完成就下一个,没完成就中断任务,调度下一个任务。每个线程执行完一个时间片,就执行调度程序。

问题:如果一个线程优先级非常高,没有必要抢占,无论如何调度,下一个还是它。希望实现最短作业优先,但是线程执行时间不可预估。

多级队列模型

image-20220726111651615

分级,紧急任务走高优先级队列,非抢占式执行。普通任务先放到小时间片队列,如果没有执行完成,说明任务不是很短,就将任务下调一层,执行更大的时间片。

线程协程

CPU视野看到的是内核空间的线程,可以在用户空间伪造协程,自己切换。

image-20220726112125761

可以N个协程绑定一个线程,在用户态完成协程切换,不会陷入到内核态。

线程是由CPU调度,抢占式;协程用户态调度,协作式,一个协程让出CPU后,才执行另一个协程。

image-20220726112535819

一个goroutine只有几KB,runtime会自动为goroutine分配不够的内存。

GMP

G: goroutine协程,M:thread线程,P:processor处理器。

image-20220726113334335

P的本地队列,存放的也是等待运行的G,不超过256个,新建的协程优先加入P的本地队列,如果满了,会将本地队列中的一般放入全局队列。

M线程想要运行任务就要获取P,从P的本地队列获取G,P队列为空时,M会尝试从全局或者其它P的本地队列steal一部分放入P的本地队列。

P和M的个数都是可以配置的,二者没有绝对关系。

当本线程因为G进行系统调用阻塞时,线程释放绑定的P,把P转移给其它空闲的线程。

goroutine创建过程和runtime的联系

runtime调度goroutine,如果goroutine内存(几KB)不够了,runtime会为goroutine分配更多内存。

runtime创建最初的线程m0和协程g0,并把二者关联,然后调度器初始化,runtime里面的main函数会调用用户main函数,创建goroutine,放入P的本地队列。然后启动m0,m0绑定了P,从P的本地队列获取G,设置运行环境,运行,然后退出,再从M中获取可以运行的G,重复。

接口的底层原理,和空接口底层实现的区别

go的接口是非侵入式的,一个结构体实现了接口定义的所有方法,就是实现了这个接口。

接口有两个结构体,eface(表示没有方法的空接口)和iface(有方法的interface类型)。

type eface struct {
    _type *_type // 底层的类型信息
    data  unsafe.Pointer // 指向的值信息指针
}
image-20220726121305479
type itab struct {
    inter *interfacetype // 接口的类型信息
    _type *_type // 具体的类型信息
    hash  uint32 // copy of _type.hash. Used for type switches.
    _     [4]byte
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
    // 存储接口方法集的具体实现,包含一组函数指针,实现了接口方法的动态分配,在每次接口发生变更时都会更新。
}

方法的值类型接受者和指针类型的接受者可以混合使用吗

不管方法的接收者是什么类型,该类型的值和指针都可以调用,不必严格符合接收者的类型。

方法和函数的区别在于方法有一个接收者,给一个函数添加一个接收者,那么它就变成了方法。

接口:接收者是值类型的方法时,会自动生成一个接受者是对应指针类型的方法,因为二者都不会影响接收者。但是,当实现了一个接收者是指针类型的方法,如果此时自动生成一个接收者是值类型的方法,原本期望对接收者的改变(通过指针实现),现在无法实现,因为值类型会产生一个拷贝,不会真正影响调用者。

如:

image-20220726130941135

但是把debug*Gopher改为Gopher,就可以通过了。

goroutine怎么使用?终止?会对其它goroutine产生影响吗?

用channel做控制,例如goroutine里面有个循环,每次判断channel是否终止。

用context来传递。

但是协程只能自己主动退出,不能A干掉B。

用过哪些go的标准库,底层原理?

os、math、time、reflect。

reflect:获取变量的类型(TypeOf里面unsafe.Pointer存放有变量的类型指针)和值信息。

Golang 并发怎么理解

GC机制

三色标记:白色未被标记、灰色表示对象已被标记但是它的子对象没有标记、黑色是当前对象和子对象都已被标记。

当默认内存扩大一倍、默认2min触发一次、runtime.gc()时触发GC。

GC最大的问题就是STW,停止所有的Goroutine。

三色标记最后只剩下黑白两种对象,只清除白色的对象不会影响程序逻辑。

根对象:全局变量,各个G的栈上的变量。

MySQL MVCC,脏读,幻读

MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作。

脏读:指在数据库访问中, 事务 T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的,值得注意的是,脏读一般是针对于update操作的。

Redis 持久化,AOF优缺点,内存淘汰

RDB恢复比AOF快。综合使用AOF和RDB两种持久化机制,用AOF来保证数据不丢失,作为数据恢复的第一选择。

lru,random,可以选择从设置过期时间的数据集里选(还有ttl,将要过期的)或者所有的数据集里选。

或者到达内存使用阈值的时候再申内存报错。

Go slice扩容机制

预估规则

image-20220726221243503

在实际分配的时候,会按照内存对齐,选择实际内存,然后微调大小。

LFU

LFU,一时半会估计只能写优先队列的实现。

四种线程池

singleThreadPool,单线程线程池

FixedThreadPool,定长线程池,超出的部分会在队列中等待。

ScheduledThreadPool,可定期或者延迟执行任务的线程池。

CachedThreadPool,可缓存线程池。(核心线程数为0)

CPU密集型:cpu核数+1,+1是由于线程偶尔由于页缺失故障等原因暂停时,也有线程再跑。

IO密集型:2xCPU核数。

实际应该根据CPU负载之类的不断调整。

spring中用到的设计模式

  • 控制反转(IOC)和依赖注入(DI)
  • 模板方法
  • 观察者模式
  • 适配器模式
  • 装饰者模式

参考文献

[Golang三关-典藏版] Golang 调度器 GMP 原理与调度全分析

线程的调度:线程调度都有哪些方法?