面试题答案
一键面试参数传递的内存机制
在Go语言中,虽然函数参数传递是值传递,但传递结构体指针时,传递的是指针的值(即内存地址)。多个协程调用带有结构体指针参数的函数时,这些协程共享该结构体指针所指向的内存空间。
可能遇到的并发安全问题
- 数据竞争:多个协程可能同时读写该结构体的成员变量,导致数据不一致。例如,一个协程读取结构体中的某个变量,同时另一个协程修改该变量,最终读取到的值可能是不确定的。
避免问题的方法
- 互斥锁(Mutex):
使用
sync.Mutex
来保护结构体的访问。在读取或修改结构体成员变量前,先获取锁,操作完成后释放锁。package main import ( "fmt" "sync" ) type MyStruct struct { data int mu sync.Mutex } func modifyStruct(s *MyStruct, newData int) { s.mu.Lock() s.data = newData s.mu.Unlock() } func main() { var wg sync.WaitGroup myStruct := MyStruct{} for i := 0; i < 10; i++ { wg.Add(1) go func(num int) { defer wg.Done() modifyStruct(&myStruct, num) }(i) } wg.Wait() fmt.Println(myStruct.data) }
- 读写锁(RWMutex):
如果读操作远多于写操作,可以使用
sync.RWMutex
。读操作时使用读锁(RLock
),写操作时使用写锁(Lock
)。package main import ( "fmt" "sync" ) type MyStruct struct { data int mu sync.RWMutex } func readStruct(s *MyStruct) int { s.mu.RLock() defer s.mu.RUnlock() return s.data } func writeStruct(s *MyStruct, newData int) { s.mu.Lock() s.data = newData s.mu.Unlock() } func main() { var wg sync.WaitGroup myStruct := MyStruct{} for i := 0; i < 5; i++ { wg.Add(1) go func(num int) { defer wg.Done() writeStruct(&myStruct, num) }(i) } for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() fmt.Println(readStruct(&myStruct)) }() } wg.Wait() }
- 通道(Channel):
可以通过通道来传递对结构体的操作请求,由一个专门的协程来处理这些请求,从而避免并发访问冲突。
package main import ( "fmt" "sync" ) type MyStruct struct { data int } type Op struct { typ string data int } func handleOps(s *MyStruct, ops <-chan Op) { for op := range ops { switch op.typ { case "write": s.data = op.data case "read": fmt.Println(s.data) } } } func main() { var wg sync.WaitGroup myStruct := MyStruct{} opsChan := make(chan Op) go handleOps(&myStruct, opsChan) for i := 0; i < 5; i++ { wg.Add(1) go func(num int) { defer wg.Done() opsChan <- Op{"write", num} }(i) } for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() opsChan <- Op{"read", 0} }() } close(opsChan) wg.Wait() }