面试题答案
一键面试设计方案
- 数据结构:
- 定义一个结构体来表示缓存数据,例如:
type Cache struct { data map[string]interface{} rwMutex sync.RWMutex writeCond sync.Cond dirty bool }
data
是实际存储缓存数据的map。rwMutex
用于读写锁控制。writeCond
是条件变量,用于通知读操作等待写操作完成。dirty
标志位用于标记是否有未完成的写操作。
- 读操作:
func (c *Cache) Read(key string) (interface{}, bool) { c.rwMutex.RLock() defer c.rwMutex.RUnlock() for c.dirty { c.writeCond.Wait() } value, exists := c.data[key] return value, exists }
- 首先获取读锁,确保读操作的并发安全性。
- 如果
dirty
标志位为true
,说明有未完成的写操作,此时通过writeCond.Wait()
等待,直到写操作完成并通知。 - 最后从
data
map 中读取数据并返回。
- 写操作:
func (c *Cache) Write(key string, value interface{}) { c.rwMutex.Lock() defer c.rwMutex.Unlock() c.dirty = true c.data[key] = value c.dirty = false c.writeCond.Broadcast() }
- 首先获取写锁,确保写操作的原子性。
- 设置
dirty
标志位为true
,表示有写操作进行中。 - 更新
data
map 中的数据。 - 写操作完成后,将
dirty
标志位设为false
,并通过writeCond.Broadcast()
通知所有等待的读操作。
不同操作场景下的工作原理
- 读操作场景:
- 多个读操作可以同时获取读锁,并行读取数据,提高了读性能。
- 当有写操作进行时,读操作会等待写操作完成(通过条件变量
writeCond
),从而保证数据一致性。
- 写操作场景:
- 写操作获取写锁,独占对数据的访问,保证写操作的原子性,避免数据竞争。
- 写操作完成后通知所有等待的读操作,让读操作可以继续执行。
性能优势
- 读性能提升:
- 结合读写锁,读操作可以并发执行,相比单纯使用互斥锁,大大提高了读操作的并发性能。在读多写少的场景下,这种性能提升尤为明显。
- 数据一致性保证:
- 通过条件变量和
dirty
标志位,确保在读操作时,如果有未完成的写操作,读操作会等待,从而保证读取到的数据是最新的,实现了数据一致性。
- 通过条件变量和
- 低延迟写操作:
- 写操作虽然需要获取写锁,但由于及时通知读操作,不会长时间阻塞读操作,保证了写操作的低延迟。同时,写操作本身的原子性也保证了数据的正确性。