Go 并发编程 - 第 1 课:Hello World 与开发环境
1. 学习目标
通过学习本课,你将能够:
- 成功安装Go语言开发环境并验证其是否工作正常。
- 理解Go项目的基本结构,并使用
go mod工具初始化一个新项目。 - 理解并区分
go run和go build命令的用途。 - 编写并运行你的第一个Go程序(Hello, World!),以及一个简单的并发示例。
- 掌握查阅Go标准库文档的基本方法。
2. 核心概念
开发环境与安装
Go语言拥有出色的跨平台支持。你需要从Go官方网站(golang.org)下载对应操作系统(Windows, macOS, Linux)的安装包,并按照指引完成安装。安装完成后,打开终端或命令行工具,输入 go version 命令。如果看到类似 go version go1.22.0 linux/amd64 的输出,恭喜你,环境已就绪。
项目结构与Go Modules
一个现代的Go项目,推荐使用 Go Modules 来管理依赖。go.mod 文件位于项目根目录,它记录了项目的模块路径和依赖关系。你可以使用 go mod init <模块名> 命令来初始化一个新模块。模块名通常是一个代码仓库的路径,例如 github.com/yourname/hello。
代码结构
Go源代码文件以 .go 为后缀。每个 .go 文件的开头必须声明它所属的 package。一个可独立运行的程序必须包含一个 main 包和一个 main() 函数,作为程序的入口点。
go run 与 go build
go run <文件或包>: 编译并直接运行程序,不会生成二进制可执行文件。非常适合开发和调试。go build [包]: 编译包及其依赖项,生成一个可执行的二进制文件(在Windows下是.exe,Linux/macOS下是同名文件)。
并发编程初探
Go语言的核心优势之一是其对并发的原生支持。本课将通过一个简单例子接触两个关键概念:
- Goroutine: 轻量级线程,由Go运行时管理。使用
go关键字加函数调用即可启动。 - Channel: 用于在Goroutine之间进行通信和同步的管道。可以看作是一个类型安全的消息队列。
3. 代码示例
示例1:经典Hello, World!
// 文件名:main.go
// 这个文件属于 `main` 包
package main
// 导入 “fmt” 包,它提供了格式化I/O函数(如打印)
import "fmt"
// main 函数是程序的入口点
func main() {
// 使用 fmt 包中的 Println 函数打印一行文本
fmt.Println("Hello, World!")
fmt.Println("欢迎来到Go并发编程的世界!")
}
如何运行:
- 在终端中进入
main.go文件所在的目录。 - 执行命令:
go run main.go
示例2:一个简单的并发示例
// 文件名:main.go
package main
import (
"fmt"
"time" // 引入time包,用于演示延时
)
// worker 函数模拟一个耗时的工作
// 参数 `id` 是工人编号, `jobs` 是接收任务的通道, `results` 是发送结果的通道
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs { // 持续从 jobs 通道接收任务
fmt.Printf("worker %d 开始处理任务 %d\n", id, j)
time.Sleep(time.Second) // 模拟耗时工作
fmt.Printf("worker %d 完成任务 %d\n", id, j)
results <- j * 2 // 将处理结果(任务值乘以2)发送到 results 通道
}
}
func main() {
const numJobs = 5
// 创建两个通道:jobs 用于分发任务,results 用于收集结果
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
// 启动3个 worker goroutine
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// 发送5个任务到 jobs 通道
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs) // 所有任务已发送,关闭 jobs 通道
// 收集所有结果
for a := 1; a <= numJobs; a++ {
fmt.Println("结果:", <-results)
}
}
如何运行:
go run main.go。观察输出,你会看到三个worker在并发地处理任务,且执行顺序是交错的。
4. 实践练习
练习1:自定义输出
创建一个新的Go模块,并编写一个程序,输出以下内容:
今天是学习Go语言的好日子!
当前时间是:<此处插入当前时间,格式为15:04:05>
提示: 你需要导入 fmt 和 time 包。使用 time.Now().Format(“15:04:05”) 来格式化时间。
练习2:扩展并发示例
在示例2的基础上,修改程序,让每个worker在完成一个任务后,除了输出完成信息,还额外输出 "工人[id] 已累计完成 [n] 个任务"。你需要在 worker 函数内部维护一个计数器。
预期输出可能类似:
worker 1 开始处理任务 1
worker 2 开始处理任务 2
worker 3 开始处理任务 3
worker 2 完成任务 2
工人2 已累计完成 1 个任务
...
练习3:通道通信挑战
编写一个程序,包含两个goroutine:sender 和 receiver。
sender向一个字符串类型的通道依次发送 “Hello”, “Go”, “Concurrency”。receiver从该通道接收这三个字符串并依次打印出来。 预期输出:
收到消息: Hello
收到消息: Go
收到消息: Concurrency
5. 常见错误
- 忘记初始化Go Modules: 在项目目录外运行代码,或没有运行
go mod init,导致包管理混乱。始终在项目根目录开始你的工作。 - 代码写在了包的外面: 所有顶级函数、变量等声明都必须在某个包内。确保你的代码行在
package main声明之后。 - 导入未使用的包: Go编译器不允许导入但不使用的包。如果导入了
“fmt”却没用到,编译会报错。这是Go强制的代码整洁规范。 - 在并发示例中,主线程没有等待goroutine完成: 在简单的并发示例中,如果main函数在goroutine完成前就退出了,那么goroutine的工作将无法看到结果。虽然示例2通过读取结果通道自然同步了,但初学者常犯的错误是启动goroutine后就直接
os.Exit或程序结束。可以使用sync.WaitGroup或确保通道操作完成来等待。 - 拼写错误: 函数名或包名大小写错误(如
println应为Println),或忘记引号。
6. 小结
本节课我们完成了Go并发编程之旅的第一步:
- 环境搭建: 安装了Go并验证了环境。
- 项目基础: 学习了使用
go mod init初始化项目,理解了Go项目的代码结构。 - 程序编译与运行: 掌握了
go run和go build的基本用法。 - 第一个程序: 成功运行了 “Hello, World!”,并接触了并发编程的基石——
goroutine和channel。 - 工具使用: 了解了查阅Go标准库文档的重要性。
你已经搭建好了舞台,并成功演出了第一幕。接下来,我们将深入学习Go语言的基石——变量与基本类型,这是构建任何复杂程序的起点。