ChatGPT 是一个强大的 AI 聊天工具,可以在编程方面起到重要的辅助作用。它能够帮助编写代码、查找错误和撰写注释,有效提升工作效率。然而,我们需要保持一定的辨别能力,因为 ChatGPT 偶尔也会提供迷惑性的答案,如果我们没有足够的辨别能力,可能就会被误导。今天,问了 ChatGPT 下面代码的执行结果。
func testDefer() {
go func() {
// 协程 1
fmt.Println("协程 1 开始执行")
time.Sleep(1 * time.Second)
fmt.Println("协程 1 结束执行")
}()
go func() {
// 协程 2
fmt.Println("协程 2 开始执行")
time.Sleep(2 * time.Second)
fmt.Println("协程 2 结束执行")
}()
defer func() {
fmt.Println("defer 语句执行")
}()
}
ChatGPT 给到的答案是:
协程 2 开始执行
协程 1 开始执行
defer 语句执行
协程 1 结束执行
协程 2 结束执行
而我在实际代码测试中却是这样的:
defer 语句执行
协程 1 开始执行
协程 2 开始执行
协程 1 结束执行
协程 2 结束执行
这样就有点令人疑惑了,到底哪个是正确的?接下来我们看看为什么会出现这样的结果。
defer
关键字
defer
用于延迟执行一个函数或方法或匿名函数,直到当前函数返回为止。defer
的主要用途是用于清理资源,比如关闭打开的文件、网络连接、数据库句柄等。这样可以减少错误的可能性,因为关闭资源的调用和打开资源的调用可以放在一起,更容易看出逻辑。
如果没有 defer 关键字,写入文件的内容的 Go 代码也许就是这样:
func writeToFile(filename, content string) error {
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return err
}
_, err = file.WriteString(content)
if err != nil {
file.Close() // 调用 Close
return err
}
// 更多操作...
file.Close() // 调用 Close
return nil
}
上面的代码中,每次函数退出前都要手动调用file.Close()
来关闭文件。这样的代码不够简洁和清晰,而且容易忘记调用,严重情况下可能导致文件资源泄漏。但是有了defer
可以很简单地避免这些问题。
func writeToFile(filename, content string) error {
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return err
}
defer file.Close() // 使用defer调用Close方法
_, err = file.WriteString(content)
if err != nil {
return err
}
// 更多操作...
return nil
}
通过使用 defer
调用file.Close()
方法,无论是正常退出还是发生异常,都能够正确地关闭文件,再也不用担心是否忘记调用了。
go
关键字
go
关键字用于启动一个新的协程(goroutine)。协程是 Go 语言提供的轻量级线程,可以并发执行,由 Go 运行时(Go runtime)负责调度。使用go
启动一个新的协程,主程序不会等待协程的执行结果,而是会继续执行后续的代码。
那么回到testDefer
函数,这个函数有两个go
语句,分别启动了两个协程,并且后面有一个defer
语句,会在函数返之前执行。两个协程执行是异步的,不会阻塞后面的代码执行,所以当执行defer
语句时,无法确定协程 1 和协程 2 是否开始打印输出,这个取决于它们的调度情况。
所以结论是,defer 语句执行可以在协程 1 开始执行和协程 2 开始执行之前执行,也可以在后面执行,这三者的顺序是不确定的。