面试题答案
一键面试潜在问题
- 数据竞争:当多个goroutine同时对
Task
类型的实例进行读写操作时,如果这些操作没有适当的同步机制,就会发生数据竞争。例如,一个goroutine调用指针接收者方法修改结构体内部状态,同时另一个goroutine调用值接收者方法读取该状态,可能导致读取到不一致的数据。 - 方法调用异常:由于Go语言值调用方法集的特性,值类型调用指针接收者方法时,Go会隐式地为值类型生成一个临时指针。但在并发场景下,这可能导致临时指针的生命周期管理问题。比如,当一个值类型调用指针接收者方法,在方法执行过程中,该值可能被其他goroutine修改,导致方法执行结果异常。
设计思路
- 使用互斥锁(Mutex):通过在方法内部使用
sync.Mutex
来保护结构体的状态,确保同一时间只有一个goroutine可以访问和修改结构体的内部数据。 - 明确方法接收者类型:对于会修改结构体状态的方法,使用指针接收者;对于只读操作的方法,根据实际需求选择值接收者或指针接收者。如果结构体较大,为了避免值拷贝带来的性能开销,建议使用指针接收者。
- 避免隐式指针转换:在并发场景下,尽量避免值类型调用指针接收者方法时的隐式指针转换,因为这可能带来潜在的风险。可以通过显式使用指针来调用方法,以提高代码的可读性和可维护性。
示例代码
package main
import (
"fmt"
"sync"
)
// Task 定义任务结构体
type Task struct {
data int
mu sync.Mutex
}
// SetData 使用指针接收者方法设置数据
func (t *Task) SetData(value int) {
t.mu.Lock()
defer t.mu.Unlock()
t.data = value
}
// GetData 使用指针接收者方法获取数据
func (t *Task) GetData() int {
t.mu.Lock()
defer t.mu.Unlock()
return t.data
}
func main() {
var wg sync.WaitGroup
task := &Task{}
// 启动多个goroutine对Task进行操作
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
task.SetData(id)
fmt.Printf("Goroutine %d: Data is %d\n", id, task.GetData())
}(i)
}
wg.Wait()
}
在上述代码中:
Task
结构体包含一个data
字段用于存储数据,以及一个sync.Mutex
类型的mu
字段用于同步访问。SetData
和GetData
方法都使用指针接收者,并且在方法内部通过mu.Lock()
和mu.Unlock()
来保护对data
字段的访问,避免数据竞争。- 在
main
函数中,启动了5个goroutine对Task
实例进行操作,通过sync.WaitGroup
来等待所有goroutine完成,确保程序在所有操作完成后退出。