面试题答案
一键面试锁的粒度控制
- 思路:减小锁保护的代码范围,只对共享资源的关键操作加锁,避免对无关操作也加锁,从而减少锁竞争。
- 示例代码:
package main
import (
"fmt"
"sync"
)
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
c.value++
c.mu.Unlock()
}
func (c *Counter) Get() int {
c.mu.Lock()
v := c.value
c.mu.Unlock()
return v
}
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.Get())
}
在这个简单示例中,Increment
和 Get
方法只对 value
变量操作部分加锁,而非整个方法体。如果有其他非共享资源操作,可以放在锁外,进一步减小锁粒度。
读写锁的合理运用
- 思路:当读操作远多于写操作时,使用读写锁(
sync.RWMutex
)。读操作可以并发执行,写操作则独占锁,这样可以提高并发性能。 - 示例代码:
package main
import (
"fmt"
"sync"
)
type Data struct {
mu sync.RWMutex
value int
}
func (d *Data) Read() int {
d.mu.RLock()
v := d.value
d.mu.RUnlock()
return v
}
func (d *Data) Write(newValue int) {
d.mu.Lock()
d.value = newValue
d.mu.Unlock()
}
func main() {
var wg sync.WaitGroup
data := Data{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
data.Write(i)
}()
}
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Read value:", data.Read())
}()
}
wg.Wait()
}
在这个例子中,读操作使用 RLock
和 RUnlock
,写操作使用 Lock
和 Unlock
,使得读操作可以并发进行,提高了并发读性能。
锁的初始化与复用
- 思路:在高并发场景下,频繁创建和销毁锁会带来额外开销。应尽量复用已初始化的锁。
- 示例代码:
package main
import (
"fmt"
"sync"
)
type Resource struct {
mu sync.Mutex
}
func (r *Resource) DoWork() {
r.mu.Lock()
// 共享资源操作
fmt.Println("Doing work on shared resource")
r.mu.Unlock()
}
func main() {
resource := Resource{}
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
resource.DoWork()
}()
}
wg.Wait()
}
这里 Resource
结构体初始化时创建了一个 sync.Mutex
锁,所有对共享资源的操作都复用这个锁,避免了频繁创建锁的开销。