腾讯云golang一面
go垃圾回收机制
参考自:https://zhuanlan.zhihu.com/p/334999060
go 1.3 标记清除法
缺点
go 1.5 三色标记法
屏障机制
插入屏障
但是如果栈不添加,当全部三色标记扫描之后,栈上有可能依然存在白色对象被引用的情况(如上图的对象9). 所以要对栈重新进行三色标记扫描, 但这次为了对象不丢失, 要对本次标记扫描启动STW暂停. 直到栈空间的三色标记结束。
删除屏障
这种方式的回收精度低,一个对象即使被删除了最后一个指向它的指针也依旧可以活过这一轮,在下一轮GC中被清理掉。
缺点
go 1.8 混合写屏障
Golang中的混合写屏障满足弱三色不变式
,结合了删除写屏障和插入写屏障的优点,只需要在开始时并发扫描各个goroutine的栈,使其变黑并一直保持,这个过程不需要STW,而标记结束后,因为栈在扫描后始终是黑色的,也无需再进行re-scan操作了,减少了STW的时间。
runtime.finalizer(终结器)
runtime.SetFinalizer 是 Go 语言中的一个函数,用于设置对象的终结器(finalizer)。终结器是一个回调函数,当对象被垃圾回收时会被调用,主要用于执行一些清理操作,如释放资源或关闭文件句柄。
package mainimport ("fmt""runtime"
)type MyStruct struct {Name string
}func main() {obj := &MyStruct{Name: "Example"}runtime.SetFinalizer(obj, func(ms *MyStruct) {fmt.Printf("Finalizing: %s\n", ms.Name)})// 使对象可回收obj = nil// 手动触发垃圾回收(仅用于测试)runtime.GC()// 等待终结器执行// time.Sleep(time.Second) // 可以加上这行来确保输出
}
二维的map按行和按列读取哪个快?
参考自 小林coding
golang里比较熟的库?
web:gin
数据库:GORM sqlx go-redis
配置管理:viper
日志管理:zap
消息队列:NSQ
微服务:go-zero
图形处理:barcode
Context放map有什么不好的?
Context 是 golang 中十分重要的接口,用于定义 goroutine 中的上下文信息,context 常用于以下几种情况:
- 数据传递: 在多个 goroutine 中传递数据
- 超时管理: 通过配置超时时间,可以方便地配置协程的终止时间
- 终止协程: 通过使用 cancel() 方法,协程可以很方便地终止,可以批量管理多个协程的终止
Context 中放 map:
- 不适合存储大对象:context 主要用于传递请求范围的数据(如截止日期、取消信号等),而不是用于存储大对象或共享状态。
- 可能导致数据竞争:如果多个 goroutine 访问同一个 map,可能会导致数据竞争,除非使用适当的同步机制。
- 增加内存压力:将大型 map 存储在 context 中可能导致不必要的内存占用,影响性能。
- 语义混淆:context 的设计初衷是为了传递请求范围的值,使用它来存储 map 可能导致代码可读性降低,其他开发者可能不容易理解上下文的使用意图。
golang基本题:创建5个协程顺序打印
package mainimport ("fmt""sync"
)var N = 5func main() {var wg sync.WaitGroupvar mu sync.Mutexch := make(chan int)wg.Add(1)go func(){defer wg.Done()for i:=1;i<=N;i++{ch<-i}close(ch)}()for i:=1;i<=N;i++{wg.Add(1)go func(){defer wg.Done()mu.Lock()// 即使协程 A 先接收到数据 1,协程 B 可能更早完成 fmt.Println 操作,导致输出顺序错乱。fmt.Println(<-ch)mu.Unlock()}()}wg.Wait()
}
如何用go来实现死锁
package mainimport ("fmt""sync""time"
)func main() {var m1, m2 sync.Mutexgo func(){// 获取第一个锁m1.Lock()defer m1.Unlock()time.Sleep(time.Second)fmt.Println("尝试获取第二个锁。。。")m2.Lock()defer m2.Unlock()fmt.Println("协程1完成")}()go func(){// 获取第一个锁m2.Lock()defer m2.Unlock()time.Sleep(time.Second)fmt.Println("尝试获取第一个锁。。。")m1.Lock()defer m1.Unlock()fmt.Println("协程2完成")}()select{}
}
线程进程区别?
进程:我们编写的代码只是一个存储在硬盘的静态文件,通过编译后就会生成二进制可执行文件,当我们运行这个可执行文件后,它会被装载到内存中,接着 CPU 会执行程序中的每一条指令,那么这个运行中的程序,就被称为「进程」(Process)。
线程:线程是进程当中的一条执行流程。同一个进程内多个线程之间可以共享代码段、数据段、打开的文件等资源,但每个线程各自都有一套独立的寄存器和栈,这样可以确保线程的控制流是相对独立的。
GMP原理调度与线程相关
参考自:https://zhuanlan.zhihu.com/p/713236782
在 Go 语言中,协程被称为 goroutine,它非常轻量,体积只有几 KB,支持自动扩容。Go 语言采用 goroutine 和 channel 提供了更容易使用的并发方法,具体为让一组可复用的函数运行在一组线程上,即使有协程阻塞,该线程的协程也可以被 runtime 调度,转移到其他可运行的线程上。
GM模型
看上去挺像那么回事,但它存在如下缺点:
每个 M 在创建、销毁、调度 G 的时候需要获取锁,形成了激烈的锁竞争。
如果 M 在执行的某个 G 涉及到创建新协程,M 创建了 G’,此时 M 为了继续执行 G,需要把 G’交给 M’执行,违背了局部性原则。(因为 G’是和 G 相关的,最好放在 M 上执行,而不是其他 M’)。
CPU 在 M 之间的频繁切换导致系统开销增大。
映射关系
GM 模型显然不是一个好的协程调度模型,但软件行业没有什么东西是加一层解决不了的,因此 P(协程调度器) 油然而生。从协程的图来看,一个用户态协程绑定了一个内核态线程,即一个协程绑定一个线程。那么是否可以利用 P,尝试不同的绑定方案?
G(协程)M(线程)P(Process)调度
- 全局 goroutine 队列,同上面介绍的 GM 模型,用于存放等待运行的 G。
- P 的本地队列:同全局队列类似,存放的也是等待运行的 G,数量不超过 256 个。新建 G’ 时,G’ 优先加入到 P 的本地队列,满足局部性。如果队列满了,会将本地队列的一半 G 和新创建的 G 打乱顺序,一起放入全局队列。
- P 列表:所有的 P 都在程序启动时创建,并保存在数组中,最多有 GOMAXPROCS (可配置)个。
- M:线程想运行任务就得获取 P,从 P 的本地队列获取 G,P 队列为空时,M 也会尝试从全局队列拿一批 G 放到 P 的本地队列,或从其他 P 的本地队列偷一半放到自己 P 的本地队列。M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去。
P 和 M 的数量
- P 的数量由 环境变量 $GOMAXPROCS 或者是由 runtime 的方法 GOMAXPROCS() 决定,当 P 的数量 n 确定以后,运行时系统会根据这个数量创建 n 个 P。
- go 程序启动时,会设置 M 的最大数量,默认 10000,但是内核很难支持这么多的线程数。
- 当没有足够的 M 来关联 P 并运行其中的可运行的 G 时,比如所有的 M 此时都阻塞住了,而 P 中还有很多就绪任务,就会去寻找空闲的 M,而没有空闲的,就会去创建新的 M。
- M 与 P 的数量没有绝对关系,一个 M 阻塞,P 就会去创建或者切换另一个 M,所以,即使 P 的默认数量是 1,也有可能会创建很多个 M。
4.调度器的设计策略
(1)work stealing 机制
当本线程无可运行的 G 时,尝试从其他线程绑定 P 的队列中偷取 G,而不是销毁线程。
(2)hand off 机制
当本线程因为 G 进行系统调用阻塞时,线程释放绑定的 P,把 P 转移给其他空闲的线程执行。
(3)利用并行
P 的数量由 GOMAXPROCS 设置,最多有 GOMAXPROCS 个线程分布在多个 CPU上 同时运行。GOMAXPROCS 也限制了并发的程度,比如 GOMAXPROCS = 核数/2,则最多利用了一半的 CPU 核进行并行。
(4)抢占
在 coroutine 中要等待一个协程主动让出 CPU 才执行下一个协程。在Go中,一个 goroutine 最多占用 CPU 10ms,防止其他 goroutine 被饿死。
(5)全局G队列
在新的调度器中依然有全局 G 队列,当 P 的本地队列为空时,优先从全局队列获取,如果全局队列为空时则通过 work stealing 机制从其他P的本地队列偷取 G。
生命周期
1千个goroutine进行阻塞系统调用会怎么样?
Go运行时(runtime)采用M:N调度模型,将goroutine复用在少量操作系统线程(M)上。当goroutine执行阻塞系统调用(如文件IO、同步网络请求)时,当前线程会被操作系统挂起。此时,Go运行时会创建新的线程(通过pthread或Windows线程)来继续运行其他goroutine。
结果:1000个阻塞系统调用会创建约1000个线程(每个阻塞调用占用一个线程)。
上限限制:Go默认允许最多10,000个线程(通过runtime/debug.SetMaxThreads设置),因此1000个线程不会触发崩溃,但需注意资源消耗。
10个linux的命令
https://blog.csdn.net/qq_24950043/article/details/126294756
cat less tail(tail -f)
scp 文件名 目标服务器账号@目标服务器ip:目标路径
pwd
ps
kill (-9)
ssh 账号@服务器ip -p 端口
nohup
grep
参考:https://blog.csdn.net/u012581020/article/details/131421817
grep match_pattern file_name
grep "match_pattern" file_name
1核,10w个goroutine会崩吗?