【Golang 面试题】每日 3 题(十四)
✍个人博客:Pandaconda-CSDN博客
📣专栏地址:http://t.csdnimg.cn/UWz06
📚专栏简介:在这个专栏中,我将会分享 Golang 面试中常见的面试题给大家~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪
40. Go slice 为什么不是线程安全的?
先看下线程安全的定义:
多个线程访问同一个对象时,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。
若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
再看 Go 语言实现线程安全常用的几种方式:
- 互斥锁
- 读写锁
- 原子操作
- sync.once
- sync.atomic
- channel
slice 底层结构并没有使用加锁等方式,不支持并发读写,所以并不是线程安全的,使用多个 goroutine 对类型为 slice 的变量进行操作,每次输出的值大概率都不会一样,与预期值不一致;slice 在并发执行中不会报错,但是数据会丢失。
/**
* 切片非并发安全
* 多次执行,每次得到的结果都不一样
* 可以考虑使用 channel 本身的特性 (阻塞) 来实现安全的并发读写
*/
func TestSliceConcurrencySafe(t *testing.T) {
a := make([]int, 0)
var wg sync.WaitGroup
for i := 0; i < 10000; i++ {
wg.Add(1)
go func(i int) {
a = append(a, i)
wg.Done()
}(i)
}
wg.Wait()
t.Log(len(a))
// not equal 10000
}
41. Golang Map 底层实现
Go 语言中的 map 是一种无序的键值对的集合,底层实现使用了哈希表(hash table)。
具体来说,Go 语言中的 map 实际上是一个指向哈希表的指针。哈希表本身是由若干个桶(bucket)组成的,每个桶包含了若干个键值对,每个键值对由一个 key 和一个 value 组成。在对 map 进行读写操作时,Go 语言会根据 key 计算出它在哈希表中的位置,然后直接访问对应的桶,从而实现高效的访问。
具体而言,当我们往 map 中添加键值对时,Go 语言会首先计算出 key 的哈希值,然后根据哈希值计算出 key 在哈希表中的位置。如果该位置还没有被占用,Go 语言会在该位置上创建一个新的桶,并把键值对放入该桶中;如果该位置已经被占用,Go 语言会在该桶中查找是否已经有一个键值对的 key 与待添加的 key 相同。
如果找到了相同的 key,就替换该键值对的 value;如果没有找到相同的 key,就将新的键值对添加到该桶的末尾。
需要注意的是,Go 语言中的 map 不是线程安全的,因此在多线程并发访问时需要使用锁等机制来保证安全。
另外,由于哈希表的大小是固定的,因此当 map 中的元素数量达到一定程度时,需要对哈希表进行扩容。
42. Go Map 扩容时机是什么?
在向 map 插入新 key 的时候,会进行条件检测,符合下面这 2 个条件,就会触发扩容
if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
hashGrow(t, h)
goto again // Growing the table invalidates everything, so try again
}
// 判断是否在扩容
func (h *hmap) growing() bool {
return h.oldbuckets != nil
}