让 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
TestGoPoolWithRetryfunction in thegopool_test.gofile. - 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
TestGoPoolWithRetryfunction, and the writing is happening in theTestGoPoolWithRetry.func1function, which is called by theexecuteTaskWithoutTimeoutfunction in theworker.gofile.
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 ...
随机推荐
- 2020-03-02:在无序数组中,如何求第K小的数?
2020-03-02:在无序数组中,如何求第K小的数? 福哥答案2021-03-02: 1.堆排序.时间复杂度:O(N*lgK).有代码. 2.单边快排.时间复杂度:O(N).有代码. 3.bfprt ...
- Spring源码:Bean生命周期(四)
前言 在之前的文章中,我们介绍了 Bean 的核心概念.Bean 定义的解析过程以及 Bean 创建的准备工作.在今天的文章中,我们将深入探讨 Bean 的创建过程,并主要讲解 createBean ...
- MMCM and PLL Dynamic Reconfiguration
Reconfiguration is performed through the DRP. The DRP provides access to the configuration bits that ...
- 【GiraKoo】Android Studio控制台乱码
[GiraKoo]Android Studio控制台乱码 启动Android Studio进行编译时,可能会遇到控制台出现异常的乱码. 本文介绍该情况的解决方案. ����: δ������쳣���� ...
- 深入理解 python 虚拟机:魔术方法之数学计算
深入理解 python 虚拟机:魔术方法之数学计算 在本篇文章当中主要给大家介绍在 python 当中一些常见的魔术方法,本篇文章主要是关于与数学计算相关的一些魔术方法,在很多科学计算的包当中都使用到 ...
- L2-004 这是二叉搜索树吗? (25 分)
1.题目描述: 一棵二叉搜索树可被递归地定义为具有下列性质的二叉树:对于任一结点, 其左子树中所有结点的键值小于该结点的键值: 其右子树中所有结点的键值大于等于该结点的键值: 其左右子树都是二叉搜索树 ...
- 代码随想录算法训练营Day52 动态规划
代码随想录算法训练营 代码随想录算法训练营Day52 动态规划| 300.最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组 300.最长递增子序列 题目链接:300.最长递增子序列 ...
- DASCTF二进制专项部分Writeup
easynote create:堆大小可以任意分配只要不超过0xFFF create() unsigned __int64 create() { int i; // [rsp+0h] [rbp-20 ...
- Java基础之基础语法与面向对象
前言 小知识 Java由Sun公司于1995年推出,2009年Sun公司被Oracle公司收购,取得Java的版权 Java之父:James Gosling(詹姆斯·高斯林) 专业术语 JDK:jav ...
- Hive执行计划之只有map阶段SQL性能分析和解读
目录 目录 概述 1.不带函数操作的select-from-where型简单SQL 1.1执行示例 1.2 运行逻辑分析 1.3 伪代码解释 2.带普通函数和运行操作符的普通型SQL执行计划解读 2. ...