面试题答案
一键面试锁的初始化设置
- 使用sync.Mutex 或 sync.RWMutex:
- sync.Mutex:在大多数简单场景下,普通的
sync.Mutex
就足够。如果读操作远远多于写操作,可以考虑sync.RWMutex
。RWMutex
允许多个读操作同时进行,只有写操作时才会独占锁,这样能提高读操作的并发性能。但要注意,写操作时会阻塞所有读操作和其他写操作。 - 初始化示例:
- sync.Mutex:在大多数简单场景下,普通的
var mu sync.Mutex
var rwmu sync.RWMutex
- 公平锁设置:Go 1.9 及以上版本,
sync.Mutex
提供了公平锁模式。可以通过设置内部状态实现公平锁。在高并发竞争场景下,公平锁能保证等待时间最长的 goroutine 优先获取锁,减少饥饿现象。- 示例:
var mu sync.Mutex
// 模拟获取公平锁
mu.Lock()
defer mu.Unlock()
在公平锁模式下,新的 goroutine 在获取锁时会先检查是否有其他 goroutine 等待锁的时间比自己长,如果有则等待。
业务逻辑设计
- 减少锁的粒度:
- 尽量将大的临界区拆分成多个小的临界区。例如,如果一个结构体有多个字段,而不同的操作只涉及部分字段,可以为每个字段或者相关字段组分别设置锁。
- 示例:
type Data struct {
field1 int
field2 string
mu1 sync.Mutex
mu2 sync.Mutex
}
func (d *Data) updateField1() {
d.mu1.Lock()
defer d.mu1.Unlock()
d.field1++
}
func (d *Data) updateField2() {
d.mu2.Lock()
defer d.mu2.Unlock()
d.field2 = "new value"
}
- 优化锁的持有时间:
- 尽量缩短持有锁的时间。在持有锁期间,只执行必要的操作,避免在锁内进行复杂的计算、I/O 操作等。
- 示例:
var mu sync.Mutex
var data []int
func processData() {
// 提前准备数据
var localData []int
{
mu.Lock()
localData = make([]int, len(data))
copy(localData, data)
mu.Unlock()
}
// 这里对 localData 进行复杂计算,不占用锁
for i := range localData {
localData[i] = localData[i] * 2
}
// 写回数据时再获取锁
mu.Lock()
data = localData
mu.Unlock()
}
- 读写分离:
- 如果业务场景允许,将读操作和写操作分开处理。读操作可以并发执行,写操作则独占锁。使用
sync.RWMutex
来实现这种读写分离的场景。 - 示例:
- 如果业务场景允许,将读操作和写操作分开处理。读操作可以并发执行,写操作则独占锁。使用
var rwmu sync.RWMutex
var data int
func readData() int {
rwmu.RLock()
defer rwmu.RUnlock()
return data
}
func writeData(newData int) {
rwmu.Lock()
defer rwmu.Unlock()
data = newData
}
其他同步工具
- sync.Cond:
sync.Cond
可以用于在多个 goroutine 之间进行条件同步。当某个条件满足时,通知等待的 goroutine。可以结合Mutex
使用,在需要等待某个条件的场景下,避免不必要的锁竞争。- 示例:
var mu sync.Mutex
var cond = sync.NewCond(&mu)
var ready bool
func worker() {
mu.Lock()
for!ready {
cond.Wait()
}
// 执行工作
mu.Unlock()
}
func activator() {
mu.Lock()
ready = true
cond.Broadcast()
mu.Unlock()
}
- sync.Map:
- 在高并发读写场景下,如果需要使用 map 结构,可以使用
sync.Map
。它内部实现了锁分离等优化机制,能在高并发下提供较好的性能,并且无需用户手动加锁。 - 示例:
- 在高并发读写场景下,如果需要使用 map 结构,可以使用
var syncMap sync.Map
func set(key string, value int) {
syncMap.Store(key, value)
}
func get(key string) (int, bool) {
val, ok := syncMap.Load(key)
if!ok {
return 0, false
}
return val.(int), true
}
- channel:
- 使用 channel 进行数据传递和同步。通过 channel 可以实现 goroutine 之间的解耦,避免使用锁来保护共享数据。例如,可以使用 buffered channel 来缓存数据,生产者和消费者通过 channel 进行数据传递,而不是直接操作共享数据。
- 示例:
func producer(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}
func consumer(ch chan int) {
for val := range ch {
// 处理数据
println(val)
}
}