面试题答案
一键面试性能特点
- 数组
- 固定长度:Go语言数组长度固定,在声明时就确定,一旦创建,大小不能改变。在并发读写时,如果数组较小且读写操作对内存布局要求较为固定,由于其内存连续分配,理论上在并发读时可能有较好的缓存命中率,因为数据在内存中是紧密排列的,CPU缓存预取机制能更好地发挥作用。
- 内存占用:由于长度固定,在并发场景下,如果数组元素较大且数组长度较长,可能会占用较多的连续内存空间,可能影响并发性能,特别是在内存紧张的情况下。
- 切片
- 动态长度:切片是动态数组,其长度可以动态变化。在并发场景下,当需要频繁增加或减少元素时,切片更加灵活。但由于其动态扩容机制,在并发读写时可能带来额外的性能开销。例如,当切片容量不足时会触发扩容,扩容操作涉及内存的重新分配和数据的复制,这在并发环境下可能导致性能问题。
- 内存管理:切片本身是一个包含指向底层数组的指针、长度和容量的结构体。在并发读写时,对切片的操作可能涉及到对结构体字段的读写,以及对底层数组的读写。如果处理不当,容易引发数据竞争问题。
可能遇到的问题
- 数据竞争
- 数组:在并发读写数组时,如果多个协程同时对数组的相同元素进行读写操作,很容易引发数据竞争问题。例如:
package main
import (
"fmt"
"sync"
)
var arr [10]int
var wg sync.WaitGroup
func write(i, val int) {
arr[i] = val
wg.Done()
}
func read(i int) {
fmt.Println(arr[i])
wg.Done()
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(2)
go write(i, i)
go read(i)
}
wg.Wait()
}
上述代码在并发读写arr
数组时,会出现数据竞争问题,导致结果不可预测。
- 切片:同样,在并发读写切片时,如果多个协程同时对切片的相同位置进行读写,或者一个协程进行写操作(如扩容、修改元素),另一个协程进行读操作,也会引发数据竞争。例如:
package main
import (
"fmt"
"sync"
)
var s []int
var wg sync.WaitGroup
func appendVal(val int) {
s = append(s, val)
wg.Done()
}
func readVal() {
fmt.Println(s)
wg.Done()
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(2)
go appendVal(i)
go readVal()
}
wg.Wait()
}
此代码在并发操作切片s
时,会出现数据竞争问题。
2. 同步问题:无论是数组还是切片,在并发读写时都需要适当的同步机制来避免数据竞争。如果使用不当的同步方式,如使用锁的粒度不当,可能会导致性能瓶颈。例如,使用全局锁保护整个数组或切片,在高并发情况下,会使得很多协程处于等待状态,降低并发性能。
不同场景下的选择
- 读多写少场景
- 数组:如果数据量固定且已知,数组是一个不错的选择。可以使用读写锁(如
sync.RWMutex
)来保护数组,读操作时可以多个协程同时进行,写操作时加写锁保证数据一致性。例如:
- 数组:如果数据量固定且已知,数组是一个不错的选择。可以使用读写锁(如
package main
import (
"fmt"
"sync"
)
var arr [10]int
var rwMutex sync.RWMutex
var wg sync.WaitGroup
func read(i int) {
rwMutex.RLock()
fmt.Println(arr[i])
rwMutex.RUnlock()
wg.Done()
}
func write(i, val int) {
rwMutex.Lock()
arr[i] = val
rwMutex.Unlock()
wg.Done()
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(2)
go write(i, i)
go read(i)
}
wg.Wait()
}
- 切片:如果需要动态增长或减少元素,切片更合适。同样可以使用读写锁保护切片。在这种场景下,由于写操作较少,切片的动态扩容带来的性能影响相对较小。
- 读写频繁且数据量动态变化场景
- 切片:切片更适合,因为其动态特性可以适应数据量的变化。但为了提高性能,需要尽量减少不必要的扩容操作。可以预先分配足够的容量(
make([]T, 0, capacity)
),以减少扩容次数。同时,使用sync.Mutex
或sync.RWMutex
来保护切片的读写操作。 - 数组:数组由于长度固定,在这种场景下不太适用,因为无法满足数据量动态变化的需求,强行使用会导致频繁的数组重建和数据复制,严重影响性能。
- 切片:切片更适合,因为其动态特性可以适应数据量的变化。但为了提高性能,需要尽量减少不必要的扩容操作。可以预先分配足够的容量(
- 数据量小且对内存布局敏感场景
- 数组:如果数据量较小且对内存布局有要求,如需要紧密排列的数据以提高缓存命中率,数组是较好的选择。例如一些简单的状态标识数组,使用数组可以使数据在内存中连续存储,在并发读时能有较好的性能表现。
- 切片:切片由于其结构体的存在以及可能的动态扩容,在这种场景下可能不如数组,因为切片的内存布局相对数组来说不够紧凑,可能影响缓存命中率。