Bootstrap

Golang|单机并发缓存

var m sync.Mutex
//sync.Mutex 是一个互斥锁,可以由不同的协程加锁和解锁。
//sync.Mutex 是 Go 语言标准库提供的一个互斥锁
//当一个协程(goroutine)获得了这个锁的拥有权后,其它请求锁的协程(goroutine)就会阻塞在 Lock() 方法的调用上,直到调用 Unlock() 锁被释放。

var set = make(map[int]bool, 0)

func printOnce(num int) {
	m.Lock()
	defer m.Unlock()
	if _, exist := set[num]; !exist {
		fmt.Println(num)
	}
	set[num] = true
}

func main() {
	for i := 0; i < 10; i++ {
		go printOnce(100)
	}
	time.Sleep(time.Second)
}
  • 接下来考虑用 sync.Mutex 封装 LRU 缓存淘汰策略的几个方法,使之支持并发的读写。
  • 抽象了一个 只读 数据结构 ByteView 用来表示缓存值,是 GeeCache 主要的数据结构之一。
  • ByteView 是对实际缓存的⼀层封装,因为实际的缓存值是⼀个 byte 切⽚存储的,⽽切⽚的底层是⼀个指向底层数组的指针,⼀个记录⻓度的变量和⼀个记录容量的变量。
  • 为什么要用 byte 切片?因为 byte 支持任意数据类型的存储。
    • byteuint8 的别名:在 Go 中,byte 实际上是 uint8 的一种别名,它表示 0255 范围内的无符号整数。
    • 在计算机中,所有数据(无论是文本、图片、视频还是其他格式)最终都会以二进制形式存储和处理。二进制数据可以被视为一系列字节([]byte),而 byte 则是这种数据的基本单位。
b := []byte{'a', 'b', 'c'}
// b的底层数据结构如下:
// 一个指针指向底层数组 [a, b, c]
// len(b) = 3
// cap(b) b的容量,取决于底层数组的大小
  • 如果获取缓存值时直接返回缓存值的切⽚,那个切⽚只是原切⽚三个变量的拷⻉,真正的缓存值就可能被外部恶意修改。
  • 这里可能一开始会有点难理解:为什么直接返回切片有问题?
    • 切片返回的是一个浅拷贝:切片的复制只会复制切片的三个元数据(指针、长度、容量),而不会复制底层数组;新切片和原切片共用同一个底层数组。
    • 缓存值可能被修改:如果外部通过返回的切片修改数据,实际上会直接修改底层数组的内容。
func main() {
    cache := []byte{'x', 'y', 'z'}
    external := cache // 浅拷贝,仅复制指针、长度和容量

    external[0] = 'a' // 修改 external 也会影响 cache
    fmt.Println(string(cache)) // 输出 "ayz"
}
  • 所以⽤ ByteView 进⾏⼀层封装,返回缓存值时的 ByteView 则是⼀个原切⽚的深拷⻉。
  • 如何通过 ByteView 解决问题?
    • ByteView 的设计目的是通过深拷贝避免直接暴露底层数组。
    • 封装数据:ByteView 持有一个不可直接访问的字段 b,存储了原始缓存数据的拷贝。外部无法直接获取和修改底层数据。
    • 深拷贝的实现:通过 cloneBytes 函数,对底层数据进行深拷贝——新建一个独立的字节数组,将原始数据逐字节复制到新数组中。
    • 只读访问:通过 ByteSliceString 等方法,外部只能访问数据的副本或只读视图,无法影响原始数据。
package geecache

// A ByteView holds an immutable view of bytes.
type ByteView struct {
	b []byte
}

// Len returns the view's length
func (v ByteView) Len() int {
	return len(v.b)
}

// ByteSlice returns a copy of the data as a byte slice.
func (v ByteView) ByteSlice() []byte {
	return cloneBytes(v.b)
}

// String returns the data as a string, making a copy if necessary.
func (v ByteView) String() string {
	return string(v.b)
}

func cloneBytes(b []byte) []byte {
	c := make([]byte, len(b))
	copy(c, b)
	return c
}
  • 接下来为 lru.Cache 添加并发特性。
;