Golang 中的 Goroutine 本地存储(Goroutine-local storage)是什么?

Goroutine 本地存储指每个 Goroutine 独立维护的存储空间。对于同一个程序,不同的 Goroutine 可以访问自己本地存储空间中的数据,但无法访问其他 Goroutine 的存储空间。

在 Golang 中,可以使用 sync.Map 作为 Goroutine 本地存储:

var localStore = sync.Map{}

func doSomething() {
    // 在当前 Goroutine 中访问本地存储
    localStore.Store("key", "value")
    value, _ := localStore.Load("key")
}

func doAnotherThing() {
    // 在其他 Goroutine 中无法访问先前存储的数据
    value, ok := localStore.Load("key")
    // ok 为 false,无法获取值
}

这里 localStore 是一个全局的 sync.Map,但是由于其内部使用 Goroutine ID 作为键名访问数据,所以每个 Goroutine 只能看到自己存放的数据,实现了本地存储的效果。
我们也可以使用原子操作来构造一个简单的 Goroutine 本地存储:

type localStore struct {
    store map[string]interface{}
    mu sync.Mutex
}

func newLocalStore() *localStore {
    return &localStore{
        store: make(map[string]interface{}), 
    }
}

func (l *localStore) Load(key string) (interface{}, bool) {
    l.mu.Lock()   // 加锁
    value, ok := l.store[key]
    l.mu.Unlock() // 解锁
    return value, ok 
}

func (l *localStore) Store(key string, value interface{}) {
    l.mu.Lock() 
    l.store[key] = value
    l.mu.Unlock()
}

这里使用 sync.Mutex 来对localStore 加锁,在每个 Goroutine 中锁住后可以访问本地 map 数据,实现了本地存储的效果。

所以 Goroutine 本地存储的主要作用是为每个 Goroutine 提供独立的数据层,使得不同 Goroutine 无法相互访问对方的本地数据。这在一定程度上可以简化并发程序的设计,避免一些竞态条件下的 bug。

但是滥用 Goroutine 本地存储也会带来数据隔离过度,降低程序结构的清晰度。所以在使用时,我们需要权衡其带来的优点与可能的负面影响。