面试题答案
一键面试并发场景下使用闭包的实际案例
假设有一个任务分发系统,需要将一批任务分发给多个goroutine并行处理,每个goroutine处理完任务后要更新共享的结果数据。
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
results := make(map[int]int)
mutex := &sync.Mutex{}
for i := 0; i < 10; i++ {
wg.Add(1)
// 使用闭包将任务逻辑封装起来
go func(taskID int) {
defer wg.Done()
// 模拟任务处理
result := taskID * taskID
mutex.Lock()
results[taskID] = result
mutex.Unlock()
}(i)
}
wg.Wait()
fmt.Println(results)
}
可能出现的问题
- 资源竞争:多个goroutine同时访问和修改
results
这个共享数据结构,可能会导致数据不一致。例如,一个goroutine正在读取results
的值,而另一个goroutine同时在修改它。
解决方法
- 使用互斥锁(Mutex):如上述代码中,通过
sync.Mutex
来保护对共享数据results
的访问。在读取或写入results
之前,调用mutex.Lock()
锁定资源,操作完成后调用mutex.Unlock()
释放锁。这样可以确保同一时间只有一个goroutine能够访问和修改results
,从而避免资源竞争和保证数据一致性。 - 读写锁(RWMutex):如果读操作远多于写操作,可以使用
sync.RWMutex
。读操作时使用RLock()
和RUnlock()
,写操作时使用Lock()
和Unlock()
。读操作可以并发进行,而写操作会独占资源,从而在保证数据一致性的同时提高性能。
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
results := make(map[int]int)
rwMutex := &sync.RWMutex{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func(taskID int) {
defer wg.Done()
result := taskID * taskID
rwMutex.Lock()
results[taskID] = result
rwMutex.Unlock()
}(i)
}
// 模拟读操作
go func() {
rwMutex.RLock()
fmt.Println(results)
rwMutex.RUnlock()
}()
wg.Wait()
}
这种方式在读多写少的场景下能有效提升性能,同时保证数据一致性。