面试题答案
一键面试1. 可能遇到的问题
- 内存泄漏:
- 在高并发环境下,如果
Set
方法不断向map
中添加新的键值对,但Delete
方法没有及时删除不再使用的键值对,随着时间推移,map
占用的内存会持续增长,导致内存泄漏。 - 例如,当应用程序有缓存过期机制,但由于并发操作,过期数据没有被及时清理,依然占用内存空间。
- 在高并发环境下,如果
- 锁争用:
- 为了保证
map
在并发操作下的线程安全,通常会使用互斥锁(如sync.Mutex
)。在高并发场景下,大量的Get
、Set
、Delete
操作会频繁竞争锁。 - 例如,许多请求同时尝试
Set
数据,每个操作都需要获取锁,当锁的竞争激烈时,会导致性能下降,因为大部分时间都花在等待锁上,而不是实际的操作上。
- 为了保证
2. 优化方案及理由
- 内存泄漏优化:
- 方案:引入缓存过期机制。可以通过在
DataCache
结构体中增加一个time.Time
类型的字段来记录每个键值对的插入时间,定期检查并删除过期的数据。 - 理由:确保不再使用的数据能及时从缓存中移除,有效避免内存泄漏。
- 方案:引入缓存过期机制。可以通过在
- 锁争用优化:
- 方案:使用读写锁(
sync.RWMutex
)来代替普通互斥锁。因为在高并发场景下,读操作通常远多于写操作,读写锁可以允许多个读操作同时进行,只有写操作需要独占锁。 - 理由:减少读操作之间的锁竞争,提高系统并发性能。
- 方案:使用读写锁(
3. 代码示例
优化前(简单互斥锁实现)
package main
import (
"fmt"
"sync"
"time"
)
type DataCache struct {
cache map[string]interface{}
mu sync.Mutex
}
func (dc *DataCache) Get(key string) (interface{}, bool) {
dc.mu.Lock()
defer dc.mu.Unlock()
value, exists := dc.cache[key]
return value, exists
}
func (dc *DataCache) Set(key string, value interface{}) {
dc.mu.Lock()
if dc.cache == nil {
dc.cache = make(map[string]interface{})
}
dc.cache[key] = value
dc.mu.Unlock()
}
func (dc *DataCache) Delete(key string) {
dc.mu.Lock()
delete(dc.cache, key)
dc.mu.Unlock()
}
func main() {
var wg sync.WaitGroup
dc := DataCache{}
start := time.Now()
for i := 0; i < 10000; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
key := fmt.Sprintf("key%d", id)
dc.Set(key, id)
value, _ := dc.Get(key)
fmt.Printf("Key: %s, Value: %v\n", key, value)
dc.Delete(key)
}(i)
}
wg.Wait()
elapsed := time.Since(start)
fmt.Printf("Total time taken: %s\n", elapsed)
}
优化后(读写锁 + 缓存过期机制)
package main
import (
"fmt"
"sync"
"time"
)
type DataCache struct {
cache map[string]cacheItem
mu sync.RWMutex
expiration time.Duration
}
type cacheItem struct {
value interface{}
timestamp time.Time
}
func (dc *DataCache) Get(key string) (interface{}, bool) {
dc.mu.RLock()
item, exists := dc.cache[key]
dc.mu.RUnlock()
if exists && time.Since(item.timestamp) < dc.expiration {
return item.value, true
}
return nil, false
}
func (dc *DataCache) Set(key string, value interface{}) {
dc.mu.Lock()
if dc.cache == nil {
dc.cache = make(map[string]cacheItem)
}
dc.cache[key] = cacheItem{
value: value,
timestamp: time.Now(),
}
dc.mu.Unlock()
}
func (dc *DataCache) Delete(key string) {
dc.mu.Lock()
delete(dc.cache, key)
dc.mu.Unlock()
}
func (dc *DataCache) Cleanup() {
for {
time.Sleep(dc.expiration / 2)
dc.mu.Lock()
for key, item := range dc.cache {
if time.Since(item.timestamp) >= dc.expiration {
delete(dc.cache, key)
}
}
dc.mu.Unlock()
}
}
func main() {
var wg sync.WaitGroup
dc := DataCache{
expiration: 2 * time.Second,
}
go dc.Cleanup()
start := time.Now()
for i := 0; i < 10000; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
key := fmt.Sprintf("key%d", id)
dc.Set(key, id)
value, _ := dc.Get(key)
fmt.Printf("Key: %s, Value: %v\n", key, value)
dc.Delete(key)
}(i)
}
wg.Wait()
elapsed := time.Since(start)
fmt.Printf("Total time taken: %s\n", elapsed)
}
通过上述代码对比,可以看到优化后的版本在高并发场景下,通过读写锁减少了锁争用,通过缓存过期机制避免了内存泄漏,从而提高了性能。