面试题答案
一键面试代码在并发执行时可能出现的问题
- 内存竞争:虽然使用了
sync.Mutex
对sharedPtr
的赋值进行了保护,但fmt.Printf("Worker %d set sharedPtr to %d\n", id, *sharedPtr)
这行代码存在内存竞争风险。因为在解锁mu
后,sharedPtr
可能在其他协程中被修改,导致这里打印的值不准确。 - 悬空指针:
local
是worker
函数的局部变量,当worker
函数返回后,local
的内存可能被释放。而sharedPtr
指向了这个局部变量,在其他协程中访问*sharedPtr
时可能访问到已释放的内存,造成悬空指针问题。
Go语言指针在并发场景下容易引发错误的原因
- 内存管理:Go语言虽然有垃圾回收机制,但对于局部变量的内存释放时机并非完全确定。在并发场景下,当一个协程结束,其局部变量内存可能被迅速回收,而其他协程可能还在通过指针访问该内存。
- 共享资源访问:多个协程同时访问和修改共享指针,容易导致数据不一致和内存竞争问题。如果没有适当的同步机制,对共享指针的读写操作可能会交错进行,产生不可预测的结果。
解决方案
- 解决内存竞争问题:在打印
*sharedPtr
时也加锁,确保在打印过程中sharedPtr
不会被其他协程修改。
package main
import (
"fmt"
"sync"
)
var sharedPtr *int
var mu sync.Mutex
func worker(id int) {
local := id * 2
mu.Lock()
sharedPtr = &local
mu.Unlock()
mu.Lock()
fmt.Printf("Worker %d set sharedPtr to %d\n", id, *sharedPtr)
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
worker(id)
}(i)
}
wg.Wait()
mu.Lock()
fmt.Printf("Final value of sharedPtr: %d\n", *sharedPtr)
mu.Unlock()
}
- 解决悬空指针问题:为
sharedPtr
分配独立的内存,而不是指向局部变量。
package main
import (
"fmt"
"sync"
)
var sharedPtr *int
var mu sync.Mutex
func worker(id int) {
newVal := id * 2
newPtr := &newVal
mu.Lock()
sharedPtr = newPtr
mu.Unlock()
mu.Lock()
fmt.Printf("Worker %d set sharedPtr to %d\n", id, *sharedPtr)
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
worker(id)
}(i)
}
wg.Wait()
mu.Lock()
fmt.Printf("Final value of sharedPtr: %d\n", *sharedPtr)
mu.Unlock()
}
通过以上修改,既能保证数据一致性,避免内存竞争,又能防止悬空指针问题。