面试题答案
一键面试可能遇到的并发问题
- 数据竞争:多个 goroutine 同时读写嵌套结构体的同一字段,导致数据不一致。比如一个 goroutine 读取某个字段值,同时另一个 goroutine 在修改该字段,最终读取到的值可能是部分修改后的无效值。
- 死锁:如果在对嵌套结构体的操作中使用了锁,并且锁的使用不当,可能会造成死锁。例如,多个 goroutine 以不同顺序获取多个锁,形成循环等待。
解决方案及优缺点
- 互斥锁(sync.Mutex)
- 实现:在访问和修改嵌套结构体的代码部分使用互斥锁进行保护。例如:
package main
import (
"fmt"
"sync"
)
type Inner struct {
Value int
}
type Outer struct {
Inner Inner
mu sync.Mutex
}
func (o *Outer) updateInnerValue(newValue int) {
o.mu.Lock()
defer o.mu.Unlock()
o.Inner.Value = newValue
}
func (o *Outer) getInnerValue() int {
o.mu.Lock()
defer o.mu.Unlock()
return o.Inner.Value
}
- **优点**:实现简单,适用于大多数场景,能够有效避免数据竞争。
- **缺点**:性能开销较大,因为每次访问和修改都需要加锁解锁,在高并发情况下可能成为性能瓶颈;如果使用不当,容易造成死锁。
2. 读写锁(sync.RWMutex) - 实现:当读操作远多于写操作时,适合使用读写锁。读操作使用读锁,写操作使用写锁。例如:
package main
import (
"fmt"
"sync"
)
type Inner struct {
Value int
}
type Outer struct {
Inner Inner
mu sync.RWMutex
}
func (o *Outer) updateInnerValue(newValue int) {
o.mu.Lock()
defer o.mu.Unlock()
o.Inner.Value = newValue
}
func (o *Outer) getInnerValue() int {
o.mu.RLock()
defer o.mu.RUnlock()
return o.Inner.Value
}
- **优点**:读操作可以并发进行,提高了高并发读场景下的性能。在写操作时会独占锁,保证数据一致性。
- **缺点**:实现相对复杂一些,需要区分读锁和写锁的使用场景;如果写操作频繁,读锁也会被阻塞,降低性能。同时,也存在死锁风险。
3. 通道(channel) - 实现:通过通道来传递对嵌套结构体的操作请求,由一个专门的 goroutine 来处理这些请求,从而避免数据竞争。例如:
package main
import (
"fmt"
"sync"
)
type Inner struct {
Value int
}
type Outer struct {
Inner Inner
}
type Op struct {
typ string
value int
reply chan int
}
func (o *Outer) worker(ch chan Op) {
for op := range ch {
switch op.typ {
case "update":
o.Inner.Value = op.value
case "get":
op.reply <- o.Inner.Value
}
}
}
- **优点**:基于 Go 的 CSP 模型,通过通信来共享内存而不是共享内存来通信,从设计理念上减少了数据竞争的可能性;可以更灵活地控制对嵌套结构体的操作顺序。
- **缺点**:增加了设计和代码实现的复杂性,需要精心设计通道的使用模式;由于操作是串行处理的,在高并发写场景下,性能可能不如读写锁优化后的方案。