# 让 GPT-4 来 review 开源社区贡献者的 PR - 每天5分钟玩转 GPT 编程系列(5)


## 1. 嘚瑟一下

你还记得那个宣称自己性能全网第一的 Golang Worker Pool 不？对，就是那个 [GoPool](https://github.com/devchat-ai/gopool)，据说作者拿着 GPT-4 只花了3天就把这个项目肝出来了。

> “那个人”发的介绍 GoPool 的文章：
> - [《仅三天，我用 GPT-4 生成了性能全网第一的 Golang Worker Pool，轻松打败 GitHub 万星项目》](https://www.danielhu.cn/golang-gopool-1/)

真的是，都不知道谦虚一下。这种文章能不被喷？能不被质疑？这篇文章发出去一周内累计阅读量破3万了，我看 GitHub 上 GoPool 项目的小星星也奔着100去了，而且开始有人提 Issue，提 PR 了……

等等，你问我为什么知道多少人质疑，为什么知道累计3万阅读量？咳咳，我就是“那个人”，我还能不知道。哈哈哈哈……

另外确实有过个别质疑，不过都没能真的拿出代码来 PK 掉 GoPool；有时候我真想说：**talk is cheap, show me the code.**

## 2. 言归正传

聊聊发生了啥。

### 2.1 GoPool 的第一个 PR

今天 GoPool 收到了第一个外部贡献者的 PR：

- [Notify adjustWorkers to exit to prevent ticker resource leak](https://github.com/devchat-ai/gopool/pull/2)

他也给 GoPool 提了第一个 issue：

- [adjustWorkers is not notified on exit, thus potential resource leak](https://github.com/devchat-ai/gopool/issues/1)

怎么回事呢，其实是他发现了 GoPool 中的这个函数没有 return 逻辑：

```go
// 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 range ticker.C {
		p.cond.L.Lock()
		if len(p.taskQueue) > len(p.workerStack)*3/4 && len(p.workerStack) < p.maxWorkers {
			// Double the number of workers until it reaches the maximum
			newWorkers := min(len(p.workerStack)*2, p.maxWorkers) - len(p.workerStack)
			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.workerStack) > p.minWorkers {
			// Halve the number of workers until it reaches the minimum
			removeWorkers := max((len(p.workerStack)-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()
	}
}
```

也就是说在 GoPool 被 Release 的时候，并不能保证这个 adjustWorkers() 函数返回，也就是对应的 goroutine 不会退出。这个问题说大不大，因为这个 goroutine 很轻量；不过确实这也是一种“内存泄露”，这个 goroutine 总归还是被停掉更优雅。

### 2.2 祭出 GPT-4

在 GitHub 上看这个 PR 还是不太清晰：

![](1.png)

一坨删除，一坨新增。懒得一行行看了，祭出 GPT-4 吧。

> 下文使用 DevChat 和 GPT-4 交互。如果你对 DevChat 感兴趣，可以跳转阅读这篇文章：
> - [《DevChat：将 GPT-4 无缝融入 VS Code，极致提升你的编程体验》](https://www.danielhu.cn/devchat-gpt4-vscode-integration/)
> - 如果你想要注册 DevChat，点击[这个链接](https://www.devchat.ai/?utm_medium=website&utm_source=hutao&utm_content=gpt-review-pr&utm_campaign=5-minutes)可以免费注册，目前 DevChat 还在公测中，不收费。

- **我问 GPT-4：**

> VS Code 里怎样 review GitHub 上的一个 PR？

- **GPT-4 这样回答：**

![](2.png)

行吧，我装一个 GitHub 插件。第一次用，还是有点小惊喜呀，眉清目秀：

![](3.png)

### 2.3 问问 GPT-4 怎么看这个 PR

我先用 `git show <commit-hash> > a.diff` 命令将这个 PR 的变更内容输出到 a.diff 文件中，方便告诉 GPT-4 当前 PR 的变更内容。

![](4.png)

如图所示，我将代码和 Diff 内容都抛给了 GPT-4，让它告诉我这个修改对不对。总之 GPT-4 说这个修改没问题，我自己也肉眼看了一遍这个修改，至少，功能上是正确的。于是我先合入了这个 PR，并且对贡献者表示感谢。

### 2.4 让 GPT-4 重构代码

我感觉用 Context 实现 goroutine 退出会更优雅一些，不过秉承着“有事 GPT 干，没事干 GPT”的原则，于是我又问 GPT-4：

> 如你所见，这段代码里用了 exitChan 来控制 goroutine 的退出，我觉得用 Context 可能更优雅，对吗？如果是的话，请帮我重构代码，引入 Context 逻辑。

![](5.png)

**GPT-4 的完整回答如下：**

首先，我们需要理解当前代码的主要功能和结构：

- 这是一个 Go 语言的 goroutine 池实现，主要包含两个文件：`gopool.go` 和 `worker.go`。
- `gopool.go` 文件定义了 goroutine 池的主要结构和方法，包括创建 goroutine 池、添加任务、等待任务完成、释放资源等。
- `worker.go` 文件定义了工作 goroutine 的主要结构和方法，包括启动工作 goroutine、执行任务、处理任务结果等。
- 当前的 goroutine 池使用 `exitChan` 来控制 goroutine 的退出。

接下来，我们将使用 `context.Context` 来替换 `exitChan`，以更优雅地控制 goroutine 的退出。我们需要做以下修改：

1. 在 `goPool` 结构中，将 `exitChan` 替换为 `ctx context.Context` 和 `cancel context.CancelFunc`。
2. 在 `NewGoPool` 函数中，使用 `context.WithCancel(context.Background())` 创建一个可取消的 context，并将返回的 context 和 cancel 函数存储到 `goPool` 结构中。
3. 在 `Release` 函数中，调用 cancel 函数来取消 context，从而通知所有的 goroutine 退出。
4. 在 `adjustWorkers` 函数中，将 `case <-p.exitChan:` 替换为 `case <-p.ctx.Done():`，以便在 context 被取消时退出 goroutine。

以下是修改后的代码：

```go path=./gopool.go
package gopool

import (
	"context"
	"sync"
	"time"
)

type goPool struct {
	// ...
	ctx    context.Context
	cancel context.CancelFunc
	// ...
}

func NewGoPool(maxWorkers int, opts ...Option) *goPool {
	ctx, cancel := context.WithCancel(context.Background())
	pool := &goPool{
		// ...
		ctx:    ctx,
		cancel: cancel,
		// ...
	}
	// ...
	return pool
}

func (p *goPool) Release() {
	close(p.taskQueue)
	p.cancel()
	// ...
}

func (p *goPool) adjustWorkers() {
	ticker := time.NewTicker(p.adjustInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			// ...
		case <-p.ctx.Done():
			return
		}
	}
}
```

这样，我们就使用 `context.Context` 替换了 `exitChan`，以更优雅地控制 goroutine 的退出。

## 3. 打完收工

这次 GPT-4 给的代码直接就能跑通，于是我追加了这个 commit：

- [Refactor goroutine pool to use context for graceful exit](https://github.com/devchat-ai/gopool/commit/2ea0ed3d821afda9732c6e14d7aa900dbabb90f6)

就这样，在首位社区贡献者 [oliver258](https://github.com/oliver258) 的提点下，外加 GPT-4 的帮助，GoPool 优雅地完成了一次 bugfix。

