面试题答案
一键面试1. 指针接收器和值接收器方法集的区别
- 指针接收器方法集:当使用指针接收器定义方法时,该方法可以修改接收器指向的值。在并发场景下,如果多个协程通过指针访问同一个对象并调用指针接收器方法,可能会导致数据竞争。例如:
package main
import (
"fmt"
"sync"
)
type Counter struct {
value int
}
func (c *Counter) Increment() {
c.value++
}
func (c *Counter) GetValue() int {
return c.value
}
func main() {
var wg sync.WaitGroup
counter := Counter{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment()
}()
}
wg.Wait()
fmt.Println("Final value:", counter.GetValue())
}
在上述代码中,多个协程并发调用 Increment
方法,由于没有同步机制,会导致数据竞争,每次运行结果可能不同。
- 值接收器方法集:使用值接收器定义的方法操作的是接收器值的副本。在并发场景下,虽然不会直接修改原始对象的值,但如果方法涉及对内部可变状态的读取,可能会因为值的复制而导致读取到不一致的数据。例如:
package main
import (
"fmt"
"sync"
)
type Data struct {
values []int
}
func (d Data) Sum() int {
sum := 0
for _, v := range d.values {
sum += v
}
return sum
}
func main() {
var wg sync.WaitGroup
data := Data{values: []int{1, 2, 3}}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
result := data.Sum()
fmt.Println("Sum:", result)
}()
}
wg.Wait()
}
如果在 Sum
方法执行期间,data.values
被其他协程修改,由于是值拷贝,Sum
方法可能读取到不一致的数据。
2. 可能导致的问题
- 数据竞争:如上述指针接收器的例子,多个协程同时修改共享数据,导致数据不一致。
- 资源不一致:值接收器方法集场景下,由于值拷贝可能读取到过时或不一致的数据,导致资源状态不一致。
3. 解决方案
- 同步机制:
- 互斥锁(Mutex):在指针接收器方法中使用互斥锁来保护共享数据。修改
Counter
示例如下:
- 互斥锁(Mutex):在指针接收器方法中使用互斥锁来保护共享数据。修改
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) GetValue() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}
func main() {
var wg sync.WaitGroup
counter := Counter{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment()
}()
}
wg.Wait()
fmt.Println("Final value:", counter.GetValue())
}
- **读写锁(RWMutex)**:对于值接收器方法中读取共享数据的场景,如果读操作远多于写操作,可以使用读写锁。例如:
package main
import (
"fmt"
"sync"
)
type Data struct {
values []int
mu sync.RWMutex
}
func (d *Data) Sum() int {
d.mu.RLock()
defer d.mu.RUnlock()
sum := 0
for _, v := range d.values {
sum += v
}
return sum
}
func (d *Data) UpdateValues(newValues []int) {
d.mu.Lock()
d.values = newValues
d.mu.Unlock()
}
func main() {
var wg sync.WaitGroup
data := Data{values: []int{1, 2, 3}}
for i := 0; i < 10; i++ {
if i%2 == 0 {
wg.Add(1)
go func() {
defer wg.Done()
data.UpdateValues([]int{4, 5, 6})
}()
} else {
wg.Add(1)
go func() {
defer wg.Done()
result := data.Sum()
fmt.Println("Sum:", result)
}()
}
}
wg.Wait()
}
- 设计模式:
- 消息传递(CSP - Communicating Sequential Processes):通过通道(channel)在协程之间传递数据,避免共享数据。例如:
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 inc := <-c.ch:
c.value += inc
}
}
}()
return &c
}
func (c *Counter) Increment() {
c.ch <- 1
}
func (c *Counter) GetValue() int {
c.ch <- 0
return c.value
}
func main() {
var wg sync.WaitGroup
counter := NewCounter()
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment()
}()
}
wg.Wait()
fmt.Println("Final value:", counter.GetValue())
}
通过通道将操作发送给负责维护 Counter
状态的协程,避免了数据竞争。