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 感兴趣,可以跳转阅读这篇文章:

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 the gopool_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 the TestGoPoolWithRetry.func1 function, which is called by the executeTaskWithoutTimeout function in the worker.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 包中的 AddInt32LoadInt32 函数来实现。这里是修改后的 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)的更多相关文章

  1. 3. Go并发编程--数据竞争

    目录 1.前言 2.数据竞争 2.1 示例一 2.1.1 测试 2.1.2 data race 检测 2.1.3 data race 配置 2.2 循环中使用goroutine引用临时变量 2.3 引 ...

  2. Java 多线程 死锁 隐性死锁 数据竞争 恶性数据竞争 错误解决深入分析 全方向举例

    在几乎所有编程语言中,由于多线程引发的错误都有着难以再现的特点,程序的死锁或其它多线程错误可能只在某些特殊的情形下才出现,或在不同的VM上运行同一个程序时错误表现不同.因此,在编写多线程程序时,事先认 ...

  3. c++11の数据竞争和互斥对象

    一.数据竞争的产生 在下面例子中: void function_1() { ; i < ; i++) { std::cout << "from function 1:&qu ...

  4. .NET 并行(多核)编程系列之七 共享数据问题和解决概述

    原文:.NET 并行(多核)编程系列之七 共享数据问题和解决概述 .NET 并行(多核)编程系列之七 共享数据问题和解决概述 前言:之前的文章介绍了了并行编程的一些基础的知识,从本篇开始,将会讲述并行 ...

  5. 如何共享数据?- 每天5分钟玩转 Docker 容器技术(41)

    数据共享是 volume 的关键特性,本节我们详细讨论通过 volume 如何在容器与 host 之间,容器与容器之间共享数据. 容器与 host 共享数据 我们有两种类型的 data volume, ...

  6. 数据收集利器 cAdvisor - 每天5分钟玩转 Docker 容器技术(82)

    cAdvisor 是 google 开发的容器监控工具,我们来看看 cAdvisor 有什么能耐. 在 host 中运行 cAdvisor 容器. docker run \ --volume=/:/r ...

  7. [Python] 文科生零基础学编程系列三——数据运算符的基本类别

    上一篇:[Python] 文科生零基础学编程系列二--数据类型.变量.常量的基础概念 下一篇: ※ 程序的执行过程,就是对数据进行运算的过程. 不同的数据类型,可以进行不同的运算, 按照数据运算类型的 ...

  8. 万能日志数据收集器 Fluentd - 每天5分钟玩转 Docker 容器技术(91)

    前面的 ELK 中我们是用 Filebeat 收集 Docker 容器的日志,利用的是 Docker 默认的 logging driver json-file,本节我们将使用 fluentd 来收集容 ...

  9. Swarm 如何存储数据?- 每天5分钟玩转 Docker 容器技术(103)

    service 的容器副本会 scale up/down,会 failover,会在不同的主机上创建和销毁,这就引出一个问题,如果 service 有要管理的数据,那么这些数据应该如何存放呢? 选项一 ...

  10. 验证 Swarm 数据持久性 - 每天5分钟玩转 Docker 容器技术(104)

    上一节我们成功将 Rex-Ray Volume 挂载到了 Service.本节验证 Failover 时,数据不会丢失. Scale Up 增加一个副本: docker service update ...

随机推荐

  1. 2023-03-03:请用go语言调用ffmpeg,摄像头捕获并编码为h264文件,不管音频。

    2023-03-03:请用go语言调用ffmpeg,摄像头捕获并编码为h264文件,不管音频. 答案2023-03-03: 使用 github.com/moonfdd/ffmpeg-go 库. 先用如 ...

  2. 2021-07-06:股票问题3。给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。注意:你不能同时参与多笔交易(

    2021-07-06:股票问题3.给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格.设计一个算法来计算你所能获取的最大利润.你最多可以完成 两笔 交易.注意:你不能同时参与多笔交易( ...

  3. vue全家桶进阶之路6:Vue的安装以及js引入

    1.安装 注意:Vue 不支持 IE8 及以下版本 创建一个文件夹用于下载引入Vue D:\BaiduSyncdisk\vue2 按照最新版本的Vue npm install vue 创建完成后便可以 ...

  4. 深度学习基础入门篇[8]::计算机视觉与卷积神经网络、卷积模型CNN综述、池化讲解、CNN参数计算

    深度学习基础入门篇[8]::计算机视觉与卷积神经网络.卷积模型CNN综述.池化讲解.CNN参数计算 1.计算机视觉与卷积神经网络 1.1计算机视觉综述 计算机视觉作为一门让机器学会如何去"看 ...

  5. linux 引导过程和服务控制

    目录 一.引导分区 二.服务控制 三.运行级别 四.systemd初始化 五.模拟错误 一.引导分区 原理:引导分区是指在开机启动到进入系统这之间的过程 引导分区的过程:1.开机自检 自检顺序:BIO ...

  6. Linux 目录 查看 压缩 编辑 命令

    目录 一.文件目录 二.查看文件 三.压缩与解压 四.vim编辑 一.文件目录结构 文件类型: /root 管理员的家目录 /home 用户家目录 /bin   命令文件目录,存放命令,管理员和用户可 ...

  7. Pandas 加载数据的方法和技巧

    哈喽大家好,我是咸鱼 相信小伙伴们在学习 python 数据分析的过程中或多或少都会听说或者使用过 pandas pandas 是 python 的一个拓展库,常用于数据分析 今天咸鱼将介绍几个关于 ...

  8. java(方法定义、调用、重载)

    1.方法 Java方法是语句的集合,它们在一起执行一个功能 方法是解决一类问题的步骤的有序组合 方法包含于类或对象中 方法在程序中被创建,在其他地方被引用 设计方法的原则:就是一个方法只完成一个功能, ...

  9. 使用镜像加速 Rtools 下载与安装

    在 windows 使用 R,尤其是安装 R 包的时候,经常会遇到一些 Rtools 的问题,今天聊一下. Rtools 是什么 Rtools 作用很大,但我们一般不怎么会直接使用. Rtools p ...

  10. ChatGLM 拉取清华git项目

    windows使用nvdia显卡运行ChatGLM 1. 安装nvidia显卡驱动 https://developer.nvidia.com/cuda-11-8-0-download-archive? ...