面试题答案
一键面试确保组合类型实现接口
在Go语言中,只要结构体实现了接口定义的所有方法,该结构体就实现了这个接口。对于结构体组合构建的复杂类型,要确保其实现接口,需要组合的结构体各自实现接口方法或者在组合类型上定义接口方法。
package main
import "fmt"
// 定义一个接口
type Printer interface {
Print()
}
// 定义一个基础结构体
type Base struct{}
func (b Base) Print() {
fmt.Println("Base Print")
}
// 定义一个组合结构体
type Composite struct {
Base
}
func main() {
var p Printer
c := Composite{}
p = c
p.Print()
}
在上述代码中,Base
结构体实现了Printer
接口的Print
方法。Composite
结构体通过组合Base
结构体,因为Composite
可以“继承”Base
的方法,所以Composite
也实现了Printer
接口。
方法集的传递和调度机制
Go语言中,方法集与指针接收器和值接收器密切相关。对于值接收器的方法,其方法集同时属于值类型和指针类型;而对于指针接收器的方法,其方法集只属于指针类型。
package main
import "fmt"
type Animal struct{}
func (a Animal) Speak() {
fmt.Println("Animal speaks")
}
func (a *Animal) Move() {
fmt.Println("Animal moves")
}
func main() {
var a Animal
var ap *Animal = &a
// 值接收器方法可以通过值或指针调用
a.Speak()
ap.Speak()
// 指针接收器方法只能通过指针调用
// a.Move() // 编译错误
ap.Move()
}
在并发场景下,值接收器的方法在并发调用时,因为值是被复制的,所以一般不会有数据竞争问题。但指针接收器的方法在并发调用时,如果涉及到共享数据,就可能出现数据竞争。
并发场景下的问题及解决方案
问题:当多个协程并发调用指针接收器的方法,且方法涉及对共享数据的修改时,可能出现数据竞争。
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 < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment()
}()
}
wg.Wait()
fmt.Println("Final value:", counter.GetValue())
}
在上述代码中,多个协程并发调用Increment
方法,由于Increment
方法修改了共享数据c.value
,可能导致最终结果不是预期的1000。
解决方案:使用sync.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 < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment()
}()
}
wg.Wait()
fmt.Println("Final value:", counter.GetValue())
}
通过在Increment
和GetValue
方法中使用sync.Mutex
,确保在同一时间只有一个协程能访问和修改共享数据,避免了数据竞争问题。