面试题答案
一键面试切片定义与使用以确保数据一致性和性能最优
-
数据一致性:为了保证多个协程同时读写切片时的数据一致性,我们可以使用
sync.Mutex
或者sync.RWMutex
。sync.Mutex
适用于读写操作都需要互斥的场景;sync.RWMutex
适用于读操作较多,写操作较少的场景,因为读操作可以并发进行,提高性能。 -
性能最优:在性能方面,尽量减少锁的持有时间,提前计算好需要的切片容量,避免频繁的内存分配。因为每次切片容量不足时进行扩容,会导致内存重新分配和数据拷贝,影响性能。
-
内存分配与垃圾回收:Go语言的垃圾回收(GC)机制是自动的。对于切片,当切片不再被引用时,其占用的内存会被GC回收。为了减少GC压力,我们应该避免频繁创建和销毁大切片。
示例代码
使用sync.Mutex
package main
import (
"fmt"
"sync"
)
var (
data []int
mutex sync.Mutex
)
func writeToSlice(num int, wg *sync.WaitGroup) {
defer wg.Done()
mutex.Lock()
data = append(data, num)
mutex.Unlock()
}
func readFromSlice(wg *sync.WaitGroup) {
defer wg.Done()
mutex.Lock()
fmt.Println(data)
mutex.Unlock()
}
func main() {
var wg sync.WaitGroup
data = make([]int, 0, 100) // 提前分配容量
for i := 0; i < 10; i++ {
wg.Add(1)
go writeToSlice(i, &wg)
}
for i := 0; i < 5; i++ {
wg.Add(1)
go readFromSlice(&wg)
}
wg.Wait()
}
使用sync.RWMutex
package main
import (
"fmt"
"sync"
)
var (
data []int
rwMutex sync.RWMutex
)
func writeToSlice(num int, wg *sync.WaitGroup) {
defer wg.Done()
rwMutex.Lock()
data = append(data, num)
rwMutex.Unlock()
}
func readFromSlice(wg *sync.WaitGroup) {
defer wg.Done()
rwMutex.RLock()
fmt.Println(data)
rwMutex.RUnlock()
}
func main() {
var wg sync.WaitGroup
data = make([]int, 0, 100) // 提前分配容量
for i := 0; i < 10; i++ {
wg.Add(1)
go writeToSlice(i, &wg)
}
for i := 0; i < 5; i++ {
wg.Add(1)
go readFromSlice(&wg)
}
wg.Wait()
}
性能分析思路
-
锁的影响:使用
sync.Mutex
时,读写操作都需要获取锁,这会导致协程等待,降低并发性能。而sync.RWMutex
允许读操作并发进行,写操作独占锁,适合读多写少的场景。可以通过go test -bench
来测试不同场景下的性能。 -
内存分配:提前分配好切片容量可以减少内存重新分配和数据拷贝的次数,提高性能。可以通过
runtime.MemStats
来监控内存分配情况,观察在不同容量分配策略下,内存分配次数和内存使用量的变化。 -
垃圾回收:频繁创建和销毁大切片会增加GC压力。可以使用
runtime.GC()
手动触发垃圾回收,观察在不同切片使用模式下,GC的频率和耗时。同时,结合runtime.MemStats
中的PauseTotalNs
字段,可以了解GC暂停时间对程序性能的影响。