Golang中context包的空指针引用问题
Golang中context包的空指针引用问题 这一行代码
return c.Context.Value(key)
是并发安全的吗?
我收到的错误堆栈跟踪已附上。

当对上下文值进行大量读取操作时,就会引发这个 panic。
是的,Context.Value 是线程/goroutine安全的。从这段代码片段中我无法看出问题所在。你为什么要把堆栈跟踪中的一行信息涂掉呢?
更多关于Golang中context包的空指针引用问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
栈中包含我们组织要求不得在任何地方公开的仓库信息。这就是我必须将其编辑掉的原因。
不过,我可以提供一些信息。 对于每个请求,我们都会将其放入请求上下文中。
*r = *r.WithContext(ctx.WithValue(r.Context(), appData, &sync.Map{}))
并获取它
if data, ok := ctx.Value(appData).(*sync.Map); ok { //这里发生了panic
data.Range(func(key, value interface{}) bool {
if k, ok := key.(string); ok {
appData[k] = value
}
return true
})
}
所有这些都通过中间件发生。
Pratim_Roy:
*r = *r.WithContext(ctx.WithValue(r.Context(), appData, &sync.Map{}))
这并非在所有机器架构上都是线程安全/goroutine安全的。
编辑:这是错误的;我原以为这是在存储一个指针,但我怀疑它实际上是存储到 Request 结构体,而这绝对不是线程安全的。
遍历 data *sync.Map 不会访问无效内存,但它是不确定的,因此如果其他 goroutine 正在添加/删除元素,你将无法确定会得到什么。
Pratim_Roy:
appData[k] = value
看起来 appData 是一个(可能是全局的?)map,而你正在向其中写入数据。这不是线程安全/goroutine安全的。
根据你提供的错误堆栈信息,这是典型的并发读写map导致的panic。context.Context的Value()方法本身是并发安全的,但问题出在你存储到context中的值。
问题分析
当你在context中存储值时,如果这个值本身不是并发安全的(比如map、slice等引用类型),并且在多个goroutine中同时读写,就会引发panic。
示例代码
package main
import (
"context"
"fmt"
"sync"
)
func main() {
// 错误示例:存储非并发安全的map
ctx := context.WithValue(context.Background(), "data", make(map[string]interface{}))
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
// 这里并发读写map会导致panic
if m, ok := ctx.Value("data").(map[string]interface{}); ok {
m[fmt.Sprintf("key%d", idx)] = idx
}
}(i)
}
wg.Wait()
}
解决方案
package main
import (
"context"
"fmt"
"sync"
"sync/atomic"
)
// 使用并发安全的map
type SafeMap struct {
mu sync.RWMutex
data map[string]interface{}
}
func NewSafeMap() *SafeMap {
return &SafeMap{
data: make(map[string]interface{}),
}
}
func (sm *SafeMap) Set(key string, value interface{}) {
sm.mu.Lock()
defer sm.mu.Unlock()
sm.data[key] = value
}
func (sm *SafeMap) Get(key string) (interface{}, bool) {
sm.mu.RLock()
defer sm.mu.RUnlock()
val, ok := sm.data[key]
return val, ok
}
func main() {
// 正确示例:存储并发安全的类型
ctx := context.WithValue(context.Background(), "data", NewSafeMap())
var wg sync.WaitGroup
var successCount int32
for i := 0; i < 100; i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
if sm, ok := ctx.Value("data").(*SafeMap); ok {
sm.Set(fmt.Sprintf("key%d", idx), idx)
atomic.AddInt32(&successCount, 1)
}
}(i)
}
wg.Wait()
fmt.Printf("成功执行次数: %d\n", successCount)
}
替代方案:使用sync.Map
package main
import (
"context"
"fmt"
"sync"
"sync/atomic"
)
func main() {
// 使用sync.Map
var safeMap sync.Map
ctx := context.WithValue(context.Background(), "data", &safeMap)
var wg sync.WaitGroup
var successCount int32
for i := 0; i < 100; i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
if m, ok := ctx.Value("data").(*sync.Map); ok {
m.Store(fmt.Sprintf("key%d", idx), idx)
atomic.AddInt32(&successCount, 1)
}
}(i)
}
wg.Wait()
fmt.Printf("成功执行次数: %d\n", successCount)
}
关键点:context.Value()方法调用本身是线程安全的,但你需要确保存储在context中的值类型是并发安全的,或者在访问时进行适当的同步控制。

