面试题答案
一键面试1. 接口设计思路
- 明确职责单一原则:每个接口应该只负责一项明确的功能,这样可以降低接口的复杂性,使得并发场景下更容易理解和维护。例如,将读操作和写操作分离到不同的接口中,避免一个接口既负责读又负责写导致的复杂逻辑和潜在竞争。
- 无状态设计:接口方法应尽量设计为无状态的。无状态的方法不会依赖对象内部的可变状态,这样在并发调用时就不会因为共享状态而产生资源竞争。
2. 并发安全的读写操作设计
- 读操作:
- 对于读多写少的场景,可以使用
sync.RWMutex
。读操作使用RLock
方法,允许多个协程同时读取数据,而不会阻塞其他读操作。 - 例如:
- 对于读多写少的场景,可以使用
package main
import (
"fmt"
"sync"
)
type Data struct {
mu sync.RWMutex
value int
}
func (d *Data) Read() int {
d.mu.RLock()
defer d.mu.RUnlock()
return d.value
}
- 写操作:
- 写操作使用
sync.Mutex
或sync.RWMutex
的Lock
方法,保证同一时间只有一个协程可以进行写操作,防止数据竞争。 - 例如:
- 写操作使用
func (d *Data) Write(newValue int) {
d.mu.Lock()
defer d.mu.Unlock()
d.value = newValue
}
3. 利用sync
包工具与接口运算协同工作
- 使用
sync.WaitGroup
:当有多个协程调用接口方法时,可以使用WaitGroup
来等待所有协程完成操作,确保程序在所有操作完成后再继续执行。- 例如:
func main() {
var wg sync.WaitGroup
data := Data{}
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
data.Write(i)
fmt.Println(data.Read())
}()
}
wg.Wait()
}
- 使用
sync.Cond
:在需要根据某个条件进行等待或唤醒协程的场景下,可以使用Condition
。例如,当数据达到某个状态时,唤醒等待的协程执行接口方法。
type Queue struct {
mu sync.Mutex
cond *sync.Cond
items []int
capacity int
}
func NewQueue(capacity int) *Queue {
q := &Queue{
capacity: capacity,
}
q.cond = sync.NewCond(&q.mu)
return q
}
func (q *Queue) Enqueue(item int) {
q.mu.Lock()
for len(q.items) == q.capacity {
q.cond.Wait()
}
q.items = append(q.items, item)
q.cond.Broadcast()
q.mu.Unlock()
}
func (q *Queue) Dequeue() int {
q.mu.Lock()
for len(q.items) == 0 {
q.cond.Wait()
}
item := q.items[0]
q.items = q.items[1:]
q.cond.Broadcast()
q.mu.Unlock()
return item
}
通过上述设计思路和sync
包工具的使用,可以在高并发的Go程序中优化接口的设计和使用,有效避免潜在的资源竞争和性能瓶颈。