面试题答案
一键面试1. 并发读写未同步
- 陷阱举例:多个 goroutine 同时读写同一个切片,会导致数据竞争。例如:
package main
import (
"fmt"
)
var s = make([]int, 0)
func write() {
for i := 0; i < 10; i++ {
s = append(s, i)
}
}
func read() {
for _, v := range s {
fmt.Println(v)
}
}
func main() {
go write()
go read()
select {}
}
此代码在多个 goroutine 同时读写 s
切片时,会引发数据竞争问题,运行结果可能是不可预测的。
- 应对方案:使用
sync.Mutex
进行同步。修改后的代码如下:
package main
import (
"fmt"
"sync"
)
var (
s = make([]int, 0)
mux sync.Mutex
)
func write() {
for i := 0; i < 10; i++ {
mux.Lock()
s = append(s, i)
mux.Unlock()
}
}
func read() {
mux.Lock()
for _, v := range s {
fmt.Println(v)
}
mux.Unlock()
}
func main() {
go write()
go read()
select {}
}
- 最佳实践:尽量减少共享数据,通过消息传递(如使用 channels)来在 goroutine 间通信,避免显式的锁操作,使代码更简洁且易于维护。
2. 切片扩容导致数据覆盖
- 陷阱举例:当多个 goroutine 同时对一个切片进行追加操作时,如果切片需要扩容,可能会导致数据覆盖。例如:
package main
import (
"fmt"
)
var s = make([]int, 0, 5)
func appendData() {
for i := 0; i < 10; i++ {
s = append(s, i)
}
}
func main() {
for i := 0; i < 2; i++ {
go appendData()
}
select {}
}
由于两个 goroutine 同时进行追加操作,可能会导致切片扩容时数据覆盖。
- 应对方案:同样可以使用
sync.Mutex
来同步追加操作。修改如下:
package main
import (
"fmt"
"sync"
)
var (
s = make([]int, 0, 5)
mux sync.Mutex
)
func appendData() {
for i := 0; i < 10; i++ {
mux.Lock()
s = append(s, i)
mux.Unlock()
}
}
func main() {
for i := 0; i < 2; i++ {
go appendData()
}
select {}
}
- 最佳实践:预先分配足够的容量,减少扩容的频率,降低数据覆盖风险。同时,使用 channels 来安全地收集来自多个 goroutine 的数据,然后再进行统一处理。
3. 数组作为函数参数时的拷贝问题
- 陷阱举例:Go 语言中数组作为函数参数是值传递,在并发环境下,如果函数对数组进行修改,可能无法影响到原数组。例如:
package main
import (
"fmt"
)
func modifyArray(arr [5]int) {
arr[0] = 100
}
func main() {
var a [5]int
go modifyArray(a)
fmt.Println(a[0])
select {}
}
这里 modifyArray
函数对数组的修改不会影响到主函数中的原数组 a
。
- 应对方案:传递数组指针。修改后的代码如下:
package main
import (
"fmt"
)
func modifyArray(arr *[5]int) {
(*arr)[0] = 100
}
func main() {
var a [5]int
go modifyArray(&a)
fmt.Println(a[0])
select {}
}
- 最佳实践:在并发编程中,如果需要对共享数组进行操作,优先考虑使用切片,因为切片本质是一个结构体,包含指向底层数组的指针,传递切片可以避免不必要的数组拷贝,提高效率。同时,要注意对切片操作的同步问题。