让 GPT-4 来修复 Golang “数据竞争”问题 - 每天5分钟玩转 GPT 编程系列(6)
1. Golang 中的“数据竞争”
我在上个月发过一篇《跟着 GPT-4 从0到1学习 Golang 并发机制(三)》,文中有一节专门介绍了“Race Detector 检测数据竞争”。
数据竞争发生在当两个或更多的 goroutine 并发访问同一块内存区域,且至少有一个访问是写入操作时。比如这段代码:
package main
import (
"fmt"
)
var counter int
func increment() {
counter++
}
func main() {
go increment()
go increment()
fmt.Println(counter)
}
在这个程序中,两个 goroutine 都在尝试增加 counter 的值。这会导致数据竞争,因为 counter++ 不是一个原子操作。
如果你使用 Race Detector 来运行这个程序(go run -race main.go),它会报告数据竞争,并给出详细的报告,包括数据竞争发生的位置和涉及的 goroutine。
2. GoPool 中的数据竞争问题
又聊到 GoPool 了。没错,还是那个宣称自己性能全网第一的 Golang Worker Pool 实现。一个普通程序员拿着 GPT-4 只花了3天就肝出来的一个高性能 Worker 池。
关于 GoPool 的介绍可以跳转这篇文章:
昨天就有人提出 GoPool 中可能存在 goroutine 泄露问题:
那位社区贡献者还提了一个 PR,然后为此我专门写了一篇文章来详细介绍这个 issue 的“前世今生”:
今天又有人提出了 GoPool 的测试用例中存在“数据竞争”问题:
在这个 issue 中贡献者贴了这样一段日志:
$ go test -v -race ./...
=== RUN TestGoPoolWithMutex
--- PASS: TestGoPoolWithMutex (0.11s)
=== RUN TestGoPoolWithSpinLock
--- PASS: TestGoPoolWithSpinLock (0.11s)
=== RUN TestGoPoolWithError
--- PASS: TestGoPoolWithError (0.11s)
=== RUN TestGoPoolWithResult
--- PASS: TestGoPoolWithResult (0.11s)
=== RUN TestGoPoolWithRetry
==================
WARNING: DATA RACE
Read at 0x00c00001c258 by goroutine 423:
github.com/devchat-ai/gopool.TestGoPoolWithRetry()
/workspaces/gopool/gopool_test.go:147 +0x284
testing.tRunner()
/usr/local/go/src/testing/testing.go:1576 +0x216
testing.(*T).Run.func1()
/usr/local/go/src/testing/testing.go:1629 +0x47
Previous write at 0x00c00001c258 by goroutine 523:
github.com/devchat-ai/gopool.TestGoPoolWithRetry.func1()
/workspaces/gopool/gopool_test.go:138 +0x64
github.com/devchat-ai/gopool.(*worker).executeTaskWithoutTimeout()
/workspaces/gopool/worker.go:78 +0xd1
github.com/devchat-ai/gopool.(*worker).executeTask()
/workspaces/gopool/worker.go:41 +0xc7
github.com/devchat-ai/gopool.(*worker).start.func1()
/workspaces/gopool/worker.go:26 +0xaa
Goroutine 423 (running) created at:
testing.(*T).Run()
/usr/local/go/src/testing/testing.go:1629 +0x805
testing.runTests.func1()
/usr/local/go/src/testing/testing.go:2036 +0x8d
testing.tRunner()
/usr/local/go/src/testing/testing.go:1576 +0x216
testing.runTests()
/usr/local/go/src/testing/testing.go:2034 +0x87c
testing.(*M).Run()
/usr/local/go/src/testing/testing.go:1906 +0xb44
main.main()
_testmain.go:61 +0x2e9
Goroutine 523 (running) created at:
github.com/devchat-ai/gopool.(*worker).start()
/workspaces/gopool/worker.go:23 +0xf7
github.com/devchat-ai/gopool.NewGoPool()
/workspaces/gopool/gopool.go:75 +0x54f
github.com/devchat-ai/gopool.TestGoPoolWithRetry()
/workspaces/gopool/gopool_test.go:134 +0xfb
testing.tRunner()
/usr/local/go/src/testing/testing.go:1576 +0x216
testing.(*T).Run.func1()
/usr/local/go/src/testing/testing.go:1629 +0x47
==================
testing.go:1446: race detected during execution of test
--- FAIL: TestGoPoolWithRetry (0.10s)
=== NAME
testing.go:1446: race detected during execution of test
FAIL
FAIL github.com/devchat-ai/gopool 0.558s
FAIL
啥?又有 issue?那我得立刻马上现在就把它“秒”掉!
3. 让 GPT-4 来修复数据竞争问题
问题很明确了,最大的问题是“日志有点长”,次要问题是“我有点懒”。没错,我是闭口不提如何从日志中寻找“哪里少加了锁导致数据竞争”。
我决定将这个问题抛给 GPT-4,让 GPT-4 直接定位问题,修复问题,再帮我写好 commit message,一条龙服务。
下文使用 DevChat 和 GPT-4 交互。如果你对 DevChat 感兴趣,可以跳转阅读这篇文章:
- 《DevChat:将 GPT-4 无缝融入 VS Code,极致提升你的编程体验》
- 如果你想要注册 DevChat,点击这个链接可以免费注册,目前 DevChat 还在公测中,不收费。
3.1 和 GPT-4 的第一轮沟通
- 我将问题抛给了 DevChat:
- DevChat 给出的回答如下:
- The user is running tests on a Go project and encountered a data race error.
- The error is occurring in the
TestGoPoolWithRetry
function in thegopool_test.go
file. - The data race is happening between two goroutines, one reading and the other writing to the same memory address.
- The reading is happening in the
TestGoPoolWithRetry
function, and the writing is happening in theTestGoPoolWithRetry.func1
function, which is called by theexecuteTaskWithoutTimeout
function in theworker.go
file.
To fix this issue, we need to ensure that the taskRunCount
variable is accessed in a thread-safe manner. We can achieve this by using a mutex to lock the variable when it's being read or written. Here is the modified TestGoPoolWithRetry
function:
func TestGoPoolWithRetry(t *testing.T) {
var retryCount = 3
var taskError = errors.New("task error")
var taskRunCount = 0
var mu sync.Mutex
pool := NewGoPool(100, WithRetryCount(retryCount))
defer pool.Release()
pool.AddTask(func() (interface{}, error) {
mu.Lock()
taskRunCount++
mu.Unlock()
if taskRunCount <= retryCount {
return nil, taskError
}
return nil, nil
})
pool.Wait()
mu.Lock()
if taskRunCount != retryCount+1 {
t.Errorf("Expected task to run %v times, but it ran %v times", retryCount+1, taskRunCount)
}
mu.Unlock()
}
In this modification, we introduced a mutex mu
and used it to lock taskRunCount
when it's being incremented and read. This ensures that only one goroutine can access taskRunCount
at a time, preventing the data race.
因为这轮聊天我发送的内容都是英文的,所以 GPT-4 也就“傲娇”地全英文响应了。扫一眼这段代码不难发现 GPT-4 通过在 taskRunCount 操作前后加互斥锁来解决“数据竞争问题。
3.2 和 GPT-4 的第二轮沟通
我觉得一个小小的 taskRunCount 数据保护,用原子操作似乎更简洁;于是我又对 DevChat 说:
是不是改成原子操作会更简洁?
- DevChat 的回答如下:
- 文字版:
是的,使用原子操作可以更简洁地解决这个问题,而且性能也会更好。我们可以使用 sync/atomic
包中的 AddInt32
和 LoadInt32
函数来实现。这里是修改后的 TestGoPoolWithRetry
函数:
import "sync/atomic"
func TestGoPoolWithRetry(t *testing.T) {
var retryCount = int32(3)
var taskError = errors.New("task error")
var taskRunCount int32 = 0
pool := NewGoPool(100, WithRetryCount(int(retryCount)))
defer pool.Release()
pool.AddTask(func() (interface{}, error) {
atomic.AddInt32(&taskRunCount, 1)
if taskRunCount <= retryCount {
return nil, taskError
}
return nil, nil
})
pool.Wait()
if atomic.LoadInt32(&taskRunCount) != retryCount+1 {
t.Errorf("Expected task to run %v times, but it ran %v times", retryCount+1, taskRunCount)
}
}
在这个修改中,我们使用 atomic.AddInt32
来原子地增加 taskRunCount
,并使用 atomic.LoadInt32
来原子地读取 taskRunCount
。这样可以确保在并发环境下对 taskRunCount
的访问是线程安全的。
3.3 提交代码
这个修改不算复杂,GPT-4 也是一遍梭哈,目测没问题(当然手测也没有问题)。于是我继续让 GPT-4 写了一个 commit message:
就这样,我提交了这个 PR:
点开看看呗,这个 PR 里的“英文描述”还是挺地道的呢!是不是给人一种“我英文还不错”的错觉?哇咔咔,GPT 在手,你都分辨不了我的母语是汉语还是英语。
4. 总结
按理说,这里需要有一个总结。
但是,我不喜欢按理说。
拜拜,我下班了。
明天见。
bye……
让 GPT-4 来修复 Golang “数据竞争”问题 - 每天5分钟玩转 GPT 编程系列(6)的更多相关文章
- 3. Go并发编程--数据竞争
目录 1.前言 2.数据竞争 2.1 示例一 2.1.1 测试 2.1.2 data race 检测 2.1.3 data race 配置 2.2 循环中使用goroutine引用临时变量 2.3 引 ...
- Java 多线程 死锁 隐性死锁 数据竞争 恶性数据竞争 错误解决深入分析 全方向举例
在几乎所有编程语言中,由于多线程引发的错误都有着难以再现的特点,程序的死锁或其它多线程错误可能只在某些特殊的情形下才出现,或在不同的VM上运行同一个程序时错误表现不同.因此,在编写多线程程序时,事先认 ...
- c++11の数据竞争和互斥对象
一.数据竞争的产生 在下面例子中: void function_1() { ; i < ; i++) { std::cout << "from function 1:&qu ...
- .NET 并行(多核)编程系列之七 共享数据问题和解决概述
原文:.NET 并行(多核)编程系列之七 共享数据问题和解决概述 .NET 并行(多核)编程系列之七 共享数据问题和解决概述 前言:之前的文章介绍了了并行编程的一些基础的知识,从本篇开始,将会讲述并行 ...
- 如何共享数据?- 每天5分钟玩转 Docker 容器技术(41)
数据共享是 volume 的关键特性,本节我们详细讨论通过 volume 如何在容器与 host 之间,容器与容器之间共享数据. 容器与 host 共享数据 我们有两种类型的 data volume, ...
- 数据收集利器 cAdvisor - 每天5分钟玩转 Docker 容器技术(82)
cAdvisor 是 google 开发的容器监控工具,我们来看看 cAdvisor 有什么能耐. 在 host 中运行 cAdvisor 容器. docker run \ --volume=/:/r ...
- [Python] 文科生零基础学编程系列三——数据运算符的基本类别
上一篇:[Python] 文科生零基础学编程系列二--数据类型.变量.常量的基础概念 下一篇: ※ 程序的执行过程,就是对数据进行运算的过程. 不同的数据类型,可以进行不同的运算, 按照数据运算类型的 ...
- 万能日志数据收集器 Fluentd - 每天5分钟玩转 Docker 容器技术(91)
前面的 ELK 中我们是用 Filebeat 收集 Docker 容器的日志,利用的是 Docker 默认的 logging driver json-file,本节我们将使用 fluentd 来收集容 ...
- Swarm 如何存储数据?- 每天5分钟玩转 Docker 容器技术(103)
service 的容器副本会 scale up/down,会 failover,会在不同的主机上创建和销毁,这就引出一个问题,如果 service 有要管理的数据,那么这些数据应该如何存放呢? 选项一 ...
- 验证 Swarm 数据持久性 - 每天5分钟玩转 Docker 容器技术(104)
上一节我们成功将 Rex-Ray Volume 挂载到了 Service.本节验证 Failover 时,数据不会丢失. Scale Up 增加一个副本: docker service update ...
随机推荐
- 2022-06-04:给定一个数字n,表示一开始有编号1~n的树木,列成一条直线, 给定一个有序数组arr,表示现在哪些树已经没了,arr[i]一定在[1,n]范围, 给定一个数字m,表示你可以补种多
2022-06-04:给定一个数字n,表示一开始有编号1~n的树木,列成一条直线, 给定一个有序数组arr,表示现在哪些树已经没了,arr[i]一定在[1,n]范围, 给定一个数字m,表示你可以补种多 ...
- 2021-05-21:给定一个数组arr,先递减然后递增,返回arr中有多少个绝对值不同的数字?
2021-05-21:给定一个数组arr,先递减然后递增,返回arr中有多少个绝对值不同的数字? 福大大 答案2021-05-21: 双指针.左指针最左,符合条件时右移:右指针最右,符合条件时左移.左 ...
- Win10环境配置(二) ——Java篇
Win10环境配置(二) --Java篇 1.工具准备 官网下载地址:[jdk-11.0.11](Java SE Development Kit 11- - Downloads (oracle.com ...
- ImageMagick 图像处理学习笔记
Use ImageMagick to create, edit, compose, or convert bitmap images. It can read and write images in ...
- 花了半天时间,使用spring-boot实现动态数据源,切换自如
在一个项目中使用多个数据源的情况很多,所以动态切换数据源是项目中标配的功能,当然网上有相关的依赖可以使用,比如动态数据源,其依赖为, <dependency> <groupId& ...
- 基于Microsoft SEAL 同态加密场景特性
基于Microsoft SEAL 同态加密场景特性 同态加密是一种特殊的加密技术,它允许在加密状态下进行计算操作而无需解密数据.在传统的加密算法中,对加密的数据进行运算操作通常需要先解密数据,然后再进 ...
- 【技术积累】Git中的基础知识【一】
Git是什么?有什么特点? Git是一个分布式版本控制系统,常用于软件开发中的源代码管理.它最初由Linux开发者Linus Torvalds创建,旨在管理Linux内核的开发. Git具有以下特点: ...
- 让AI支持游戏AI模型:从经典AI算法到最新技术的应用
目录 20. 让 AI 支持游戏AI模型:从经典 AI 算法到最新技术的应用 1. 引言 2. 技术原理及概念 2.1 基本概念解释 2.2 技术原理介绍 2.2.2 最新技术介绍 3. 实现步骤与流 ...
- 文件系统考古 3:1994 - The SGI XFS Filesystem
在 1994 年,论文<XFS 文件系统的可扩展性>发表了.自 1984 年以来,计算机的发展速度变得更快,存储容量也增加了.值得注意的是,在这个时期出现了更多配备多个 CPU 的计算机, ...
- UE5打包SDK未正确安装的问题
正文 Windows(笔者之前用的电脑是windows10,最新电脑使用的是windows11)下UE5打包项目的需要安装Visual Studio. 而且安装的时候需要选择上C++ 游戏开发相关模块 ...