面试题答案
一键面试问题分析
- 数据竞争问题:当多个goroutine同时调用
Increment
方法,由于Counter
结构体中的数据会被并发访问和修改,会出现数据竞争问题。这会导致最终的结果不可预测,因为多个goroutine可能同时读取和修改Counter
的内部状态,从而产生不一致的数据。 - 方法绑定角度:虽然
Increment
方法使用指针接收者,确保了对同一Counter
实例的操作,但这种绑定并不能解决并发访问的冲突。每个goroutine在调用Increment
时,都认为自己是对Counter
实例进行独占操作,从而导致竞争。
解决方案
- 使用互斥锁(Mutex):
在
Counter
结构体中添加一个sync.Mutex
字段,在Increment
方法中,在修改数据前加锁,修改完成后解锁。示例代码如下:
package main
import (
"fmt"
"sync"
)
type Counter struct {
value int
mu sync.Mutex
}
func (c *Counter) Increment() {
c.mu.Lock()
c.value++
c.mu.Unlock()
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}
- 使用读写锁(RWMutex):
如果
Counter
有读操作(如获取当前值),并且读操作远多于写操作,可以使用sync.RWMutex
。读操作使用读锁,写操作(如Increment
)使用写锁。示例代码如下:
package main
import (
"fmt"
"sync"
)
type Counter struct {
value int
mu sync.RWMutex
}
func (c *Counter) Increment() {
c.mu.Lock()
c.value++
c.mu.Unlock()
}
func (c *Counter) Value() int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.value
}
- 使用通道(Channel):
通过通道来控制对
Counter
的访问,将对Counter
的操作发送到一个专用的goroutine中执行,避免直接并发访问。示例代码如下:
package main
import (
"fmt"
"sync"
)
type Counter struct {
value int
ch chan int
}
func NewCounter() *Counter {
c := &Counter{ch: make(chan int)}
go func() {
for {
select {
case <-c.ch:
c.value++
}
}
}()
return c
}
func (c *Counter) Increment() {
c.ch <- 1
}
func (c *Counter) Value() int {
return c.value
}
这种方式通过将操作序列化到一个goroutine中,避免了数据竞争。但需要注意通道的正确使用,确保所有操作都通过通道进行。