面试题答案
一键面试方法集动态变化带来的复杂性
- 数据竞争:多个 goroutine 同时修改对象的方法集时,可能导致数据竞争。这会使得程序的行为不可预测,可能出现读脏数据、写覆盖等问题。
- 一致性问题:如果一个 goroutine 在修改方法集的同时,另一个 goroutine 尝试调用方法集中的方法,可能会调用到一个不一致或者未完全初始化的方法集,导致程序崩溃或者出现逻辑错误。
可能出现的问题
- 方法调用失败:当一个 goroutine 正在删除某个方法,而另一个 goroutine 正要调用该方法时,会导致方法调用失败,因为方法可能已经不存在了。
- 内存安全问题:例如,在方法集动态变化过程中,可能会出现悬空指针的情况,当方法集引用的函数对象被删除,但仍被其他 goroutine 尝试调用时,会引发内存安全问题。
同步机制及代码示例
- 使用互斥锁(
sync.Mutex
):互斥锁可以用来保护方法集的修改操作,确保同一时间只有一个 goroutine 可以修改方法集。
package main
import (
"fmt"
"sync"
)
type MethodSet struct {
mu sync.Mutex
methods map[string]func()
}
func (ms *MethodSet) AddMethod(name string, method func()) {
ms.mu.Lock()
defer ms.mu.Unlock()
if ms.methods == nil {
ms.methods = make(map[string]func())
}
ms.methods[name] = method
}
func (ms *MethodSet) RemoveMethod(name string) {
ms.mu.Lock()
defer ms.mu.Unlock()
delete(ms.methods, name)
}
func (ms *MethodSet) CallMethod(name string) {
ms.mu.Lock()
method, ok := ms.methods[name]
ms.mu.Unlock()
if ok {
method()
} else {
fmt.Printf("Method %s not found\n", name)
}
}
func main() {
ms := MethodSet{}
var wg sync.WaitGroup
wg.Add(2)
go func() {
ms.AddMethod("test", func() {
fmt.Println("Test method called")
})
wg.Done()
}()
go func() {
ms.CallMethod("test")
wg.Done()
}()
wg.Wait()
}
在上述代码中:
MethodSet
结构体包含一个sync.Mutex
用于同步访问,以及一个map
用于存储方法集。AddMethod
和RemoveMethod
方法在修改方法集前加锁,操作完成后解锁,确保同一时间只有一个 goroutine 可以修改方法集。CallMethod
方法在读取方法集前也加锁,防止在读取过程中方法集被修改,从而避免数据竞争和不一致问题。
- 使用读写锁(
sync.RWMutex
):如果读操作(调用方法)频繁,写操作(添加或删除方法)相对较少,可以使用读写锁。读写锁允许多个 goroutine 同时读,但只允许一个 goroutine 写。
package main
import (
"fmt"
"sync"
)
type MethodSet struct {
mu sync.RWMutex
methods map[string]func()
}
func (ms *MethodSet) AddMethod(name string, method func()) {
ms.mu.Lock()
defer ms.mu.Unlock()
if ms.methods == nil {
ms.methods = make(map[string]func())
}
ms.methods[name] = method
}
func (ms *MethodSet) RemoveMethod(name string) {
ms.mu.Lock()
defer ms.mu.Unlock()
delete(ms.methods, name)
}
func (ms *MethodSet) CallMethod(name string) {
ms.mu.RLock()
method, ok := ms.methods[name]
ms.mu.RUnlock()
if ok {
method()
} else {
fmt.Printf("Method %s not found\n", name)
}
}
func main() {
ms := MethodSet{}
var wg sync.WaitGroup
wg.Add(3)
go func() {
ms.AddMethod("test", func() {
fmt.Println("Test method called")
})
wg.Done()
}()
go func() {
ms.CallMethod("test")
wg.Done()
}()
go func() {
ms.CallMethod("test")
wg.Done()
}()
wg.Wait()
}
在这个代码示例中:
AddMethod
和RemoveMethod
方法使用写锁(Lock
),确保在修改方法集时,没有其他 goroutine 可以读或写。CallMethod
方法使用读锁(RLock
),允许多个 goroutine 同时调用方法,提高了并发读的效率。