1. 概述

没错,又有人给 GoPool 项目提 issue 了:

和往常一样,我借着 DevChat 提供的 GPT-4 能力来解这个 issue。今天我准备以这个 issue 为例,和大家介绍下使用 DevChat 编程的常用操作。

2. Bug 分析与复现

用户写了这样一段代码:

package main

import (
"fmt"
"math/rand"
"sync"
"time" "github.com/devchat-ai/gopool"
) func main() {
var m sync.Map
pool := gopool.NewGoPool(
20,
gopool.WithMinWorkers(3),
)
defer pool.Release()
var i = 0
for {
i += 1
if _, ok := m.Load(i); !ok {
pool.AddTask(func() (interface{}, error) {
m.Store(i, 1)
task(i)
m.Delete(i)
return nil, nil
})
}
if i > 20 {
break
}
}
pool.Wait()
} func task(t int) error {
i := rand.Intn(20)
time.Sleep(time.Second * time.Duration(i))
fmt.Printf("> %d -- %d \n", t, i)
return nil
}

然后发现这个程序执行后会遇到这样的报错:

$ go run main.go
> 21 -- 0
> 21 -- 0
> 21 -- 5
> 21 -- 0
> 21 -- 0
panic: runtime error: slice bounds out of range [:-4] goroutine 52 [running]:
github.com/devchat-ai/gopool.(*goPool).adjustWorkers(0x14000108000)
/Users/danielhu/go/pkg/mod/github.com/devchat-ai/gopool@v0.6.0/gopool.go:173 +0x1c4
created by github.com/devchat-ai/gopool.NewGoPool
/Users/danielhu/go/pkg/mod/github.com/devchat-ai/gopool@v0.6.0/gopool.go:95 +0x3e8

我试了下可以稳定复现,上面这段日志就是在我本地执行的输出内容。每次执行报错的前几行可能内容不一样,不过下面抛异常的那一行是确定的,也就是 gopool.go:173

好吧,那就先把这一段代码扒出来看看。第173行代码是:

p.workerStack = p.workerStack[:len(p.workerStack)-removeWorkers]

这行代码在下面这个函数里面:

// adjustWorkers adjusts the number of workers according to the number of tasks in the queue.
func (p *goPool) adjustWorkers() {
ticker := time.NewTicker(p.adjustInterval)
defer ticker.Stop() for {
select {
case <-ticker.C:
p.cond.L.Lock()
if len(p.taskQueue) > len(p.workers)*3/4 && len(p.workers) < p.maxWorkers {
// Double the number of workers until it reaches the maximum
newWorkers := min(len(p.workers)*2, p.maxWorkers) - len(p.workers)
for i := 0; i < newWorkers; i++ {
worker := newWorker()
p.workers = append(p.workers, worker)
p.workerStack = append(p.workerStack, len(p.workers)-1)
worker.start(p, len(p.workers)-1)
}
} else if len(p.taskQueue) == 0 && len(p.workers) > p.minWorkers {
// Halve the number of workers until it reaches the minimum
removeWorkers := max((len(p.workers)-p.minWorkers)/2, p.minWorkers)
p.workers = p.workers[:len(p.workers)-removeWorkers]
p.workerStack = p.workerStack[:len(p.workerStack)-removeWorkers]
}
p.cond.L.Unlock()
case <-p.ctx.Done():
return
}
}
}

你也可以在 GitHub 上的这个地址找到这行代码。

3. Bug 定位与修复

可能你对这段代码不太熟悉,感觉看起来费劲。没关系,我看起来也费劲,毕竟一来这段代码主要是 GPT 写的,二来也有些天了,我也忘得差不多了。

第173行出错的话,不难想到大概率是 removeWorkers 的值计算有问题,也就是 171 行的:

removeWorkers := max((len(p.workers)-p.minWorkers)/2, p.minWorkers)

缩容,具体怎么缩的我和你一样记不清,不过没关系,我们知道缩容要干的事情是“当 Pool 空闲的时候,将 workers 的数量缩减到最小数量”就行。那么 max((len(p.workers)-p.minWorkers)/2, p.minWorkers) 这个算式的逻辑大概率就不对了。假如当前 workers 数量是9,minWorkers 值是5,这个算式的结果是 max((9-5)/2, 5),也就是 max(2, 5),结果是5,9-5=4,4!=5,确实不对。

但是应该怎么改呢?,于是我就把这个函数丢给 DevChat,告诉它我想要的结果,看下 DevChat 能不能将其修复:

如图所示,首先我们需要将这个函数作为 Context 告诉 DevChat,然后基于这段 Context 来提问。

DevChat 说 removeWorkers := max((len(p.workers)-p.minWorkers)/2, p.minWorkers) 这一行是错的,应该改成 removeWorkers := (len(p.workers) - p.minWorkers + 1) / 2

当 DevChat 给出新代码之后,你可以用上图这种方式,通过 diff 视图来“预览” GPT 到底改了哪些行。

看起来只有一行,不过数学运算,边界考虑啥的,还是有点烧我的“CPU”。我没法一眼看出来这个新的等式对不对,于是,掰手指吧:

假设当前 workers 是 10,minWorkers 是 5,那么:

  1. 第一轮 removeWorkers 应该是 (10 - 5 + 1) / 2 = 3,也就是说要 remove 掉 3 个 workers,剩下 7 个;
  2. 第二轮 removeWorkers 应该是 (7 - 5 + 1) / 2 = 1,也就是说要 remove 掉 1 个 workers,剩下 6 个;
  3. 第三轮 removeWorkers 应该是 (6 - 5 + 1) / 2 = 1,也就是说要 remove 掉 1 个 workers,剩下 5 个;

至此 workers 和 minWorkers 相等,任务完成。行吧,看起来没毛病。

4. 代码测试

开头那段代码重新跑一遍,发现还是报错,我的嘴巴半分钟没合拢……

细看目前这段缩容逻辑:

if len(p.taskQueue) == 0 && len(p.workers) > p.minWorkers {
// Halve the number of workers until it reaches the minimum
removeWorkers := max((len(p.workers)-p.minWorkers)/2, pminWorkers)
p.workers = p.workers[:len(p.workers)-removeWorkers]
p.workerStack = p.workerStack[:len(p.workerStack)-removeWorkers]
}

哦,当 taskQueue 为空的时候,只是任务队列里没有新任务了,但是 workers 可能还在工作中,这时候执行缩容,相当于有一定概率 kill 掉了正在工作的 workers…… 不过这个问题是偶现的,因为缩容动作1秒才触发一次,而之前在 UT 里覆盖的场景是1秒内 workers 已经全部执行完,进入 idle 状态,所以缩容不会出错。但是恰好这次用户的 task 里面有一个 time.Sleep(time.Second * time.Duration(i)) 的逻辑,也就是随机 Sleep。好吧,我服。

另外在 GoPool 里 worker 的 push 和 pop 逻辑是这样的:

func (p *goPool) popWorker() int {
p.lock.Lock()
workerIndex := p.workerStack[len(p.workerStack)-1]
p.workerStack = p.workerStack[:len(p.workerStack)-1]
p.lock.Unlock()
return workerIndex
} func (p *goPool) pushWorker(workerIndex int) {
p.lock.Lock()
p.workerStack = append(p.workerStack, workerIndex)
p.lock.Unlock()
p.cond.Signal()
}

也就是说当有 worker 在工作的时候,workerStack 里保存的 index 会被 pop 出去,因此可以得到2个结论:

  1. 当 len(workerStack) == len(workers) 的时候表示 Pool 空闲;
  2. 当 Pool 繁忙时执行扩容,会导致 workerStack 中的 index 无序。(比如 [1,2,3] 这个初始 index 切片在工作中可能变成了 [1,2],然后扩容后会变成 [1,2,4,5],接着空闲了又变成 [1,2,4,5,3])

复杂度又上来了一点。首先 if 的条件需要加上“空闲判断”,于是就得这样写了:

if len(p.taskQueue) == 0 && len(p.workerStack) == len(p.workers) && len(p.workers) > p.minWorkers

接着要解决无序问题,加个排序。我也不记得排序应该调用哪个库了,另外上面这段分析我也不确定是不是一定正确,还是发给 GPT 吧:

最后 DevChat 给出的新代码里这个 if 长这样:

if len(p.taskQueue) == 0 && len(p.workerStack) == len(p.workers) && len(p.workers) > p.minWorkers {
// Halve the number of workers until it reaches the minimum
removeWorkers := (len(p.workers) - p.minWorkers + 1) / 2
// Sort the workerStack before removing workers
sort.Ints(p.workerStack)
p.workers = p.workers[:len(p.workers)-removeWorkers]
p.workerStack = p.workerStack[:len(p.workerStack)-removeWorkers]
}

再测一次开头那段代码,ok 了。

5. 文档更新

写文档还是挺让程序员头疼,写英文文档更加头疼。还好现在有 GPT 了,咱继续将需要更新的文档作为上下文,发给 DevChat:

DevChat 插件提供了一个“响应内容一键复制”按钮,点一下就能将 GPT 生成的原始 Markdown 格式文本复制到剪切板,这样就能轻松地将包含代码块的文档内容插入到 README.md 文件里了。

6. 提交 Commit

别再写只有一个“update”的 commit message 了,在 DevChat 里可以直接将 git diff 内容发送给 GPT,然后让 GPT 根据当前修改总结一个 commit message 出来:

  1. 执行 git add,然后将 diff 内容加入 Context:

  1. 执行 commit_message 命令:

  1. 获取 GPT 总结的 commit message,可以直接在 DevChat 页面 commit,也可以复制后手动执行 git commit:

7. 总结

要想让 GPT 输出令人满意的答案,一定要给它精准的 Context(上下文)。如果使用 DevChat 你可以灵活地选择一段代码、几段代码或者几个源文件加入 Context,然后基于这些选中的 Context 向 GPT 提问。

最后,别忘了 DevChat 的注册地址:www.devchat.ai

当开源项目 Issue 遇到了 DevChat的更多相关文章

  1. iOS开发之开源项目链接

    1. Coding iOS 客户端 Coding官方客户端. 笔者强烈推荐的值得学习的完整APP.GitHub - Coding/Coding-iOS: Coding iOS 客户端源代码 2. OS ...

  2. 开源项目在真机调试(Coding iOS 客户端为例)

    一.前言 iOS 13学习系列:如何在github下载开源项目到本地(Coding iOS 客户端为例)已经把 Coding iOS 客户端源码下载到本地. 但项目进行真机调试遇到很多问题. 二.问题 ...

  3. 直接拿来用!最火的Android开源项目(一) (转)

    对于开发者而言,了解当下比较流行的开源项目很是必要.利用这些项目,有时能够让你达到事半功倍的效果.为此,CSDN特整理了GitHub上最受欢迎的Android及iOS开源项目,本文详细介绍了20个An ...

  4. Android开源项目分包方式学习(eoe、oschina、github)

    总感觉Android中关于分包的文章很少,或者几乎可以说没有.但是合理地分包,又可以使整个项目模块化,减少包与包之间的依赖,让整个项目的框架更加清晰,更利于后续功能的拓展. 因为没有相关的文章,所以这 ...

  5. GitHub 优秀的 Android 开源项目(转)

    今天查找资源时看到的一篇文章,总结了很多实用资源,十分感谢原作者分享. 转自:http://blog.csdn.net/shulianghan/article/details/18046021 主要介 ...

  6. 直接拿来用!最火的Android开源项目

    GitHub在中国的火爆程度无需多言,越来越多的开源项目迁移到GitHub平台上.更何况,基于不要重复造轮子的原则,了解当下比较流行的Android与iOS开源项目很是必要.利用这些项目,有时能够让你 ...

  7. GitHub 优秀的 Android 开源项目

    转自:http://blog.csdn.net/shulianghan/article/details/18046021 主要介绍那些不错个性化的View,包括ListView.ActionBar.M ...

  8. 转载__直接拿来用!最火的Android开源项目(一)

    http://www.csdn.net/article/2013-05-03/2815127-Android-open-source-projects 已分类汇总到 https://github.co ...

  9. 最火的Android开源项目(一)

    GitHub在中国 的火爆程度无需多言,越来越多的开源项目迁移到GitHub平台上.更何况,基于不要重复造轮子的原则,了解当下比较流行的Android与iOS开源项 目很是必要.利用这些项目,有时能够 ...

  10. 【转】GitHub平台最火Android开源项目整理——2013-08-25 17

    http://game.dapps.net/news/developer/9199.html GitHub在中国的火爆程度无需多言,越来越多的开源项目迁移到GitHub平台上.更何况,基于不要重复造轮 ...

随机推荐

  1. uniapp 全局背景音乐播放+暂停(跳转页面不暂停)

    最近需要一个功能 是在h5中播放小游戏的背景音乐,但是跳转界面之后音乐不暂停,就是跳转多个页面之后,音乐依然在播放,在游戏界面会有设置的静音的按钮,可以开启音乐和关闭音乐. 单独建了一个music.j ...

  2. 【python基础】复杂数据类型-字典(嵌套)

    有时候,需要将一系列字典存储在列表中,或将列表作为值存储在字典中,这称为嵌套.我们可以在列表中嵌套字典.在字典中嵌套列表.在字典中嵌套字典. 1.列表嵌套字典 我们可以把一个人的信息放在字典中,但是多 ...

  3. .NET指定图片地址下载并转换Base64字符串

    需求描述 需要调用第三方图片上传接口上传图片,对方图片格式只能接收Base64字符串.所以我们需要将系统服务器的图片通过Url下载下来,然后转换成Base64字符串.接下来我们将使用HttpClien ...

  4. const 使用

    宏定义与const的区别?(概念题是最容易丢分)1. 发生时机不一样: 宏定义发生在预处理时,const关键字发生编译时2. 宏定义仅仅只做了字符串的替换,没有类型检查; const关键字有类型检查, ...

  5. CatBoost的分布式训练与调优:解决大规模数据集问题

    目录 <CatBoost 的分布式训练与调优:解决大规模数据集问题> 引言 随着深度学习的兴起,大规模数据集的存储和处理成为一个重要的技术挑战.由于数据集的规模巨大,传统的分布式训练方法已 ...

  6. ArrayList 扩容机制

    ArrayList 基本介绍 ArrayList实现了List接口.它可以存储包括null的任何类型的对象,允许重复元素.ArrayList在内部使用一个数组来存储元素,当元素数量超过数组容量时,Ar ...

  7. golang 实现四层负载均衡

    大家好,我是蓝胖子,做开发的同学应该经常听到过负载均衡的概念,今天我们就来实现一个乞丐版的四层负载均衡,并用它对mysql进行负载均衡测试,通过本篇你可以了解到零拷贝的应用,四层负载均衡的本质以及实践 ...

  8. Unity的OnOpenAsset:深入解析与实用案例

    Unity OnOpenAsset 在Unity中,OnOpenAsset是一个非常有用的回调函数,它可以在用户双击资源文件时自动打开一个编辑器窗口.这个回调函数可以用于自定义资源编辑,提高工作效率. ...

  9. 使用docker安装的tomcat部署activiti-app.war、activiti-admin.war失败(ClassNotFoundException)

    背景 一直以来习惯用docker配置一些本地学习环境,许多教程配置activiti的方式都是通过复制activiti的war包部署在tomcat中,我尝试了一下通过docker的方式遇到了一些不易察觉 ...

  10. Mybatis(配置解析解读(核心))

    核心配置文件 mybaits-confing.xml *properties(属性) *settring(设置) *typeAliases(类型别名) *typeHandlers(类型处理器) *ob ...