Q:init 函数的执行顺序
每个包和每个源文件都可以有多个Init 函数,且同一个包的init执行顺序,golang没有明确定义。Init 先于main 函数执行,且不能被其他函数调用。 导入包的时候无依赖关系的先导入,导入后就会执行init 函数
package main
import "fmt"
func init() {
fmt.Println("calling init")
}
var testA int64 = initTestA()
func initTestA() int64 {
fmt.Println("calling initTestA")
return 100
}
func main() {
fmt.Println("calling main")
}
代码输出:
calling initTestA
calling init
calling main
初始化顺序:变量初始化->init()->main()
Q: Slice扩容
调用runtime.growslice对切片进行扩容,主要步骤如下:
func growslice(et *_type, old slice, cap int) slice{
// 1.检查并确定扩容的cap
// 2.内存对齐并计算出需要的内存,检查是否越界
// 3.申请新的内存并将旧的数据进行复制,返回新的slice结构体
}
Q:Slice 跟 数组的区别
1.初始化时数组必须指明长度,slice 不用
2.数组[4]int 和 [5]int 是不同的类型
3.数组传递是将数组整个拷贝进行传递,函数内修改不影响外面数组的值,而切片则是对引用复制的传递,如果修改了切片有可能会影响外面的切片。注:如果传入切片修改时进行了扩容或是其他方式改变了Slice 的引用,那么不会影响外部Slice的值
4.数组的长度不可变,但是Slice可以进行扩容
5.用slice截取数组时,修改slice会影响到数组。因为slice就是对原数组某一段的引用。
Q:局部对象是分配到栈上还是分配到堆上,返回一个引用安全吗
Golang 中的变量只要被引用就会一直存活,Golang 编译器会将函数的局部变量分配到栈帧上,如果编译器不能确保变量return 后不再被引用,那么编译器会将它分配到堆上。所以这里返回一个引用是安全的。 并且如果局部变量非常大,那么它需要被分配在堆上而不是栈上。 并且Go 分成了微对象(0,16B)、小对象(16B,32KB)、大对象(32KB ,+∞),微对象通过微分配器提高分配的性能,大对象会分配到栈上。
Q:map 是线程安全的吗,有什么包可以让其线程安全的访问
map不是线程安全的,多个协程并发读写同一个 key 的时候,会出现冲突,导致 panic。 安全访问的方式: 1.加锁读写。如果对性能有要求可以通过分片加锁来减少锁颗粒度
type SafeMap struct {
sync.RWMutex
m map[int]int
}
func (m *SafeMap) Get(key int) (int, bool) {
m.Lock()
defer m.Unlock()
v, exists := m[key]
return v, exists
}
func (m *SafeMap) Set(key, value int) {
m.Lock()
defer m.Unlock()
m.m[key] = value
}
2.go 提供的sync.map。适合于读多写少的场景。对于写多的场景,会导致 read map 缓存失效,需要加锁,导致冲突变多;而且由于未命中 read map 次数过多,导致 dirty map 提升为 read map,这是一个 O(N) 的操作,会进一步降低性能。
Q:defer的执行顺序
越晚声明的defer 越先执行,是一个栈类型的执行顺序。
Q:有缓冲的channel 和 无缓冲的channel 有什么区别
无缓冲的会等到数据被读取后才继续往下执行,是阻塞的,而有缓冲的如果缓冲区还有空间则写入数据后继续往下执行。
Q:GO 的GC
这部分内容可以看 Go设计与实现:垃圾收集器
Q: 讲一讲select ,select 怎么执行的
select
允许在一个goroutine中管理多个channel。但是,当所有channel同时就绪的时候,go需要在其中选择一个执行。 select
如果没有一个case channel就绪,那么他就会运行default:
,如果 select
中没有写default,那么他就进入等待状态
Q:Go的GMP调度模型, GMP 之间的数量 ,为什么是这种数量关系?为什么要抽象出P?
G:Goroutine,每个Gotoutine对应一个G结构体,G存储Goroutine的运行堆栈,状态,以及任务函数,可重用(函数实体)G需要保存到P才能被调度执行 M:machine,os内核线程抽象,代表真正执行计算的资源,在绑定有效的P后,进入schedule循环 M的数量是不固定的,有Go Runtime调整,为了防止创建过多OS线程导致系统调度不过来,目前默认设置为10000个,M不保存G的上下文,这是G可以跨M的基础。 P:Processor,表示逻辑处理器,对G来说,P相当于CPU核,G只有绑定到P才能被调度。对M来说,P提供了相关的执行环境,入内存分配状态,任务队列等。 P 的数量决定了系统内最大可并行的 G 的数量(前提:物理 CPU 核数 >= P 的数量)。 P 的数量由用户设置的 GoMAXPROCS 决定,但是不论 GoMAXPROCS 设置为多大,P 的数量最大为 256。
抽象出P的原因:
- 调度器和锁是全局资源,所有的调度状态都是中心化存储的,锁竞争问严重;
- 线程需要经常互相传递可运行的 Goroutine,引入了大量的延迟;
- 每个线程都需要处理内存缓存,导致大量的内存占用并影响数据局部性;
- 系统调用频繁阻塞和解除阻塞正在运行的线程,增加了额外开销;
Q:Go 如果 P 太多怎么处理
P太多,更准确地说是你系统的核数太多,会导致调度消耗增多,调度等待时长增多,性能反而下降
Q:go内存模型,程序申请内存之后干了什么事
1.去P 找到对应的 mcache , 去拿对应跨度类的mspan 2.如果没有,那么就去mcenter 看有没有,如果mcenter 没有则去mheap 请求,然后再分配回来。 3.mcache 的一些mspan 用完了可以还回到 mcenter 详细内容可参见:Go设计与实现-内存分配器
Q:Go 携程泄露有什么情况,怎么做排查
发生的情况如 父携程结束但是子携程被卡住没能正确结束,则会造成内存泄露。 可以通过pprof 文件对程序的携程情况进行分析。
Q:sync.Once 底层如何实现的
type Once struct {
done uint32
m Mutex
}
通过done 来控制执行一次,并且在加锁后会通过二次检查来确保加锁过程中没有被其他的携程执行。
Q:hashmap 什么可以做key什么不可以?
可以做等值比较的才可以做map的Key
Q:Go携程是可抢占的吗
Go 1.14之前是不可抢占的,是携程主动的gopark 进行退出。1.14以后则是基于信号的抢占
Q:Context的作用,如何做到通知携程的。
推荐阅读:Go语言之Context
Q:Interface 在底层的结构体是怎么样的
// interface{} 类型不包含任何方法,所以它的结构也相对来说比较简单
type eface struct { // 16 字节
_type *_type
data unsafe.Pointer
}
// 用于表示接口的结构体
type iface struct { // 16 字节
tab *itab
data unsafe.Pointer
}
Q:Go 深拷贝一个对象
如果对象中没有引用类型,那么直接浅拷贝(直接申请一个变量赋值)即可,如果有,那么需要对引用类型进行一个拷贝,不然仍然会指向同一个引用。可以基于序列化和反序列化来实现对象的深度拷贝:
func deepCopy(dst, src interface{}) error {
var buf bytes.Buffer
if err := gob.NewEncoder(&buf).Encode(src); err != nil {
return err
}
return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst)
}
Q:如何确定一个channel 是否关闭
package main
import "fmt"
func main() {
c := make(chan int, 10)
c <- 1
close(c)
for {
v, isOpen := <-c
if !isOpen {
fmt.Println("Channel 已经关闭")
return
}
fmt.Println(v)
}
}
输出:
1
Channel 已经关闭
可以通过读取channel 的第二个值判断是否关闭,但是无法即时判断,如果有残留数据则会将数据读取完才返回关闭。 如果channel 已经关闭那么读取channel 返回的是默认值,所以需要判断第二个返回值看是否已经关闭。
Q:timer 和 ticker 有什么区别
ticker只要定义完成,从此刻开始计时,不需要任何其他的操作,每隔固定时间都会触发。 timer定时器,是到固定时间后会执行一次 如果timer定时器要每隔间隔的时间执行,实现ticker的效果,使用 func (t *Timer) Reset(d Duration) bool