原文地址:Go面试看这里了~(二十一)
1、sync.Map的实现原理?
先看下src/sync/map.go文件中几个相关结构体的数据结构:
type Map struct {
// 当涉及到脏数据(dirty)操作时候,需要使用这个锁
mu Mutex
// read是一个只读数据结构,包含一个map结构,
// 读不需要加锁,只需要通过 atomic 加载最新的值即可
read atomic.Value // readOnly
// dirty 包含部分map的键值对,如果操作需要mutex获取锁
// 最后dirty中的元素会被全部提升到read里的map去
dirty map[interface{}]*entry
// misses是一个计数器,用于记录read中没有的数据而在dirty中有的数据的数量。
// 也就是说如果read不包含这个数据,会从dirty中读取,并misses+1
// 当misses的数量等于dirty的长度,就会将dirty中的数据迁移到read中
misses int
}
sync.Map的原理是通过分离读写map和原子指令来实现读的近似无锁,并通过延迟更新的方式来保证读的无锁化,其主要思想就是读写分离,空间换时间。
sync.Map的优点:
-
空间换时间:通过冗余的两个数据结构(read、dirty),实现加锁对性能的影响。
-
使用只读数据(read),避免读写冲突。
-
动态调整:miss次数多了之后,将dirty数据迁移到read中。
-
double-checking。
-
延迟删除,删除一个键值只打标记,在迁移dirty数据时才清理删除的数据。
-
优先从read读取、更新、删除,因为对read的读取不需要锁。
2、可以限制运行时操作系统线程的数量吗?
可用环境变量GOMAXPROCS或runtime.GOMAXPROCS(num int)设置,GOMAXPROCS限制的是同时执行用户态Go代码的操作系统线程的数量,但是对于被系统调用阻塞的线程数量是没有限制的,GOMAXPROCS的默认值等于CPU的逻辑核数,同一时间,一个核只能绑定一个线程,然后运行被调度的goroutine,因此对于CPU密集型的任务,若该值过大,例如设置为CPU逻辑核数的2倍,会增加线程切换的开销,降低性能,对于I/O密集型应用,适当地调大该值,可以提高I/O吞吐率。
3、协程泄露(Goroutine Leak)?
Go并发是以goroutine和channel的形式实现,协程泄露指的是goroutine创建后长时间得不到释放,且还在不断创建新goroutine,最终导致内存耗尽,程序崩溃。
常见导致协程泄露的场景如下
-
缺少接收器,导致发送阻塞:启动N个协程接收channel中信息,但channel并不会发送那么多信息,从而导致接收协程阻塞,不能退出。
-
死锁(dead lock):同一个goroutine中使用同一个chnnel读写,其次是2个以上的Go程中使用同一个channel通信,且读写channel先于Go程序创建,再次就是channel和读写锁、互斥锁混用。
-
无限死循环(infinite loops):I/O 操作上的堵塞也可能造成泄露,例如发送请求到 API 服务器,而没有使用超时;或者程序单纯地陷入死循环中。
至此,本次分享就结束了,后期会慢慢补充。
以上仅为个人观点,不一定准确,能帮到各位那是最好的。
好啦,到这里本文就结束了,喜欢的话就来个三连击吧。
扫码关注公众号,获取更多优质内容。