网上找了一些golang的面试题并且自己写上了自己的解答。
- Go里有哪些数据结构是并发安全的?int类型是并发安全的吗?
int类型并不安全,对它的加减操作编译成底层机器码是有多条的,并发安全需要它的操作是原子的,os实现原子操作的方式有硬件支持cas,锁总线等方式。32位赋值操作是原子,64位赋值操作则是非原子的。
sync包内有并发安全的数据结构: Mutex/RWMutex/WaitGroup/Map/tomic/Channel。 - Go如何实现一个单例模式?
全局变量+init函数;sync.Once封装实例初始化过程,保证只执行一次;双重检查懒加载。 - sync.Once是如何实现的,如何不使用sync.Once实现单例模式?
Once维护了一个原子整数和一个锁,调用过Do函数整数会+1,只有整数为0的时候才会执行函数。每次调用Do都是加锁执行的。可以用双重检查来实现单例。 - Go语言里的map是并发安全的吗?
不是,想使用安全的map要用sync.Map。 - 如果要实现一个并发安全的map,该怎么做?
分桶加锁,参考 https://github.com/seymourtang/concurrency-map/blob/master/map.go - Slice与数组的区别?Slice的底层实现?复制和传值时,是深拷贝还是浅拷贝?
slice本质上是维护了数组的一个视图,结构体内是数组指针、len和cap。赋值和传值都是浅拷贝。 - sync.map的底层实现?
syncMap有一个readmap和dirtymap,读值的时候先去readmap查,查不到再穿透到dirtymap。dirtyMap是写值会使用。思想就是空间换时间、读写分离。syncMap适合读多写少的场景。需要读写并发的map可以自己实现一个分桶加锁的map。 - sync.mutex的底层实现?
mutex是互斥锁,有normal和starvation两种模式,不支持重入。mutex维护了两个变量,state锁状态和sema信号量。state维护了waiter、woken、starving、locked的状态。locked=1就是加锁状态。waiter就是阻塞的协程数量。协程在normal状态加锁时会自旋一段时间再获取锁,标记woken=1,此时解锁协程只需要释放锁不需要去唤醒协程。如果阻塞的协程被唤醒后发现锁被抢走了,则再次阻塞,判定阻塞时常超过1ms则会标记starving,不会启动自旋过程,下次解锁肯定会把锁交给阻塞协程。 - 如何判断一个结构体是否实现了某接口?
使用断言_, ok := someValue.(SomeInterface)
- channel底层实现?什么时候会panic?
channel底层实现就是一个循环数组实现的消息队列,用来保存缓冲的消息。还有发送者和接受者的阻塞队列。panic的情况:未初始化时关闭、重复关闭、关闭后发送、发送时关闭。
参考: https://zhuanlan.zhihu.com/p/544363104 - 介绍GMP模型,相比于正常的协程-线程-进程调度,有哪些优点和缺点?
G(Goroutine)M(Mechine)P(Processor),调度器会启动M(核数)个线程,每个线程维护一个Processor,P维护一个Goroutine队列。还有一个公共Goroutine队列。当P的工作队列空了之后会拿别的P的Goroutine来运行。GMP模型比传统的协程优点在于自动调度,透明,轻量级。比传统协程实现复杂,传统协程需要手动管理。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。