原文地址:Go面试看这里了~(十四)
1、main和init的区别?
main和init的相同点:定义时不能有任何参数或返回值,且Go程序自动调用。
main和init的不同点:
-
init可应用于任意包中,且可重复定义多个。
-
main只能用于main包中,且只能定义一个。
main和init的执行顺序:
-
对同一Go文件的init调用顺序是从上到下。
-
对同一package中不同文件的init是按文件名称字符串大小从小到大顺序调用。
-
对不同package,如不相互依赖,则按main中先import后调用顺序调用init。
-
对不同package,如相互依赖,则先调用最早被依赖的package的init。
2、什么情况下Go runtime会创建一个协程?
在Go程序中可能有数千Go协程运行在一个线程中,如该线程中任一Go协程阻塞(如等待用户输入),此时Go会创建一个新OS线程并将其余Go协程移动到此线程,以上操作都是runtime来完成。
3、原生map线程安全吗?为什么?怎么实现线程安全map操作?
原生map线程不安全,在同一时间段内,不同goroutine对同一字典读写,其本身可能会因这些操作而产生混乱,相关程序也可能会因此出现不可预知的问题。
Go sync包提供线程安全的map,主要还是配合(锁)实现线程安全的map,主要实现方式有以下几种:
-
悲观锁:进来的每一步操作都认为同时会有其他进程影响操作,所以提前加锁。
-
乐观锁:因为map线程不安全的,可能是因为读-写或写-写造成的,所以在map写的时候加上锁就会提高map的性能。
-
根据map实现原理,在buckets或map更基本的组成层面加锁,根据乐观锁的情况进行小范围加锁。
sync.Map的实现原理可概括为以下几点:
-
通过read和dirty两字段将读写分离,读的数据存在只读字段read上,将最新写入的数据存在dirty字段上。
-
读取时会先查询read,不存在再查询dirty,写入时则只写入dirty。
-
读取read并不需要加锁,而读或写dirty都需要加锁。
-
使用misses字段统计read被穿透的次数(被穿透指需要读dirty的情况),超过一定次数则将dirty数据同步到read上。
-
删除数据直接通过标记来延迟删除。
来看下sync.Map的使用案例:
var ma sync.Map // 只需要声明即可使用
ma.Store("key", "value") // 存储值
ma.Delete("key") // 删除值
ma.LoadOrStore("key", "value") // 获取值,如果没有则存储
fmt.Println(ma.Load("key")) // 获取值
ma.Range(func(key, value interface{}) bool { // 遍历
fmt.Printf("key:%s ,value:%s \n", key, value)
return true // return一个true,继续执行,return一个false直接退出循环
})
map底层写的很棒,但为了效率,并未实现线程安全,所以另外加了sync.map,兼容线程安全,sync.map实现是依靠两张map对读操作和写操作分离,后续根据需要将dirty map合入read map中,相对于乐观锁实现的方式,写进程执行的时候,读进程也可能在read map上进行。
至此,本次分享就结束了,后期会慢慢补充。
以上仅为个人观点,不一定准确,能帮到各位那是最好的。
好啦,到这里本文就结束了,喜欢的话就来个三连击吧。
扫码关注公众号,获取更多优质内容。