golang支持线程安全和自动过期map

发布于:2024-12-08 ⋅ 阅读:(121) ⋅ 点赞:(0)

在 Golang 中,原生的 map 类型并不支持并发安全,也没有内置的键过期机制。不过,有一些社区提供的库和方案可以满足这两个需求:线程安全和键过期。


1. 使用 sync.Map(线程安全,但不支持过期)

Golang 提供了线程安全的 sync.Map,但它没有键过期功能。如果只需要线程安全,可以直接使用:

import (
	"fmt"
	"sync"
)

func main() {
	var m sync.Map

	m.Store("key1", "value1") // 写入键值
	val, ok := m.Load("key1") // 读取键值
	if ok {
		fmt.Println("key1:", val)
	}

	m.Delete("key1") // 删除键值
}

限制sync.Map 适用于高并发场景,但需要自行实现键的过期功能。


2. 使用开源库 go-cache(推荐:支持线程安全和键过期)

go-cache 是一个轻量级、高效的内存缓存库,支持线程安全和键过期功能。

安装
go get github.com/patrickmn/go-cache
使用示例
package main

import (
	"fmt"
	"time"

	"github.com/patrickmn/go-cache"
)

func main() {
	// 创建一个缓存对象,默认过期时间为 5 分钟,清理间隔为 10 分钟
	c := cache.New(5*time.Minute, 10*time.Minute)

	// 设置键值,并指定过期时间
	c.Set("key1", "value1", cache.DefaultExpiration) // 默认过期时间
	c.Set("key2", "value2", 10*time.Second)         // 自定义过期时间

	// 读取键值
	val, found := c.Get("key1")
	if found {
		fmt.Println("key1:", val)
	} else {
		fmt.Println("key1 has expired or not found")
	}

	// 检查键是否存在
	_, exists := c.Get("key2")
	fmt.Println("key2 exists:", exists)

	// 删除键
	c.Delete("key2")
}
特点
  • 线程安全。
  • 支持键过期,自动清理。
  • 提供多种方法(如读取、删除、批量操作等)。

3. 使用 expiremap(支持自动过期和并发安全)

expiremap 是另一个简洁的库,专门为自动过期的键值存储设计。

安装
go get github.com/zyedidia/expiremap
使用示例
package main

import (
	"fmt"
	"time"

	"github.com/zyedidia/expiremap"
)

func main() {
	// 创建一个过期 map,键值过期时间为 2 秒
	m := expiremap.New(time.Second * 2)

	// 设置键值
	m.Set("key1", "value1")
	m.Set("key2", "value2")

	// 读取键值
	val, ok := m.Get("key1")
	if ok {
		fmt.Println("key1:", val)
	} else {
		fmt.Println("key1 has expired or does not exist")
	}

	// 等待 3 秒后,键值会自动过期
	time.Sleep(3 * time.Second)
	_, ok = m.Get("key1")
	fmt.Println("key1 exists after 3 seconds:", ok)
}
特点
  • 键过期时间由 time.Duration 控制。
  • 自动清理过期键。
  • 支持线程安全。

4. 自己实现一个安全且支持过期的 map

如果你不想使用外部库,可以结合 sync.RWMutextime.Timer 自行实现:

示例代码
package main

import (
	"fmt"
	"sync"
	"time"
)

type SafeMap struct {
	data  map[string]any
	mutex sync.RWMutex
}

func NewSafeMap() *SafeMap {
	return &SafeMap{
		data: make(map[string]any),
	}
}

func (sm *SafeMap) Set(key string, value any, duration time.Duration) {
	sm.mutex.Lock()
	defer sm.mutex.Unlock()
	sm.data[key] = value

	// 启动一个定时器删除键
	go func() {
		time.Sleep(duration)
		sm.mutex.Lock()
		delete(sm.data, key)
		sm.mutex.Unlock()
	}()
}

func (sm *SafeMap) Get(key string) (any, bool) {
	sm.mutex.RLock()
	defer sm.mutex.RUnlock()
	val, ok := sm.data[key]
	return val, ok
}

func (sm *SafeMap) Delete(key string) {
	sm.mutex.Lock()
	defer sm.mutex.Unlock()
	delete(sm.data, key)
}

func main() {
	sm := NewSafeMap()

	sm.Set("key1", "value1", 5*time.Second) // 设置 5 秒过期

	val, ok := sm.Get("key1")
	fmt.Println("key1 exists:", ok, "value:", val)

	// 等待 6 秒,确保键已过期
	time.Sleep(6 * time.Second)

	val, ok = sm.Get("key1")
	fmt.Println("key1 exists after expiration:", ok)
}
特点
  • sync.RWMutex 确保并发安全。
  • 使用 time.Timer 实现键过期。

总结

  • 如果需要简单易用的解决方案,推荐使用 go-cache
  • 如果你需要更轻量的库,expiremap 是一个好选择。
  • 对于特定需求,可以自行实现线程安全的 map,结合定时器实现过期功能。

网站公告

今日签到

点亮在社区的每一天
去签到