最近非常流行询问DeepSeek某些相关概念或热点的解释,因此在开始系统性地学习《Go语言圣经》之前,我首先向DeepSeek进行了提问。具体的Prompt如下:有关Golang当中的Goroutines和Channels,我现在要对这两个知识点进行系统性地学习,请您深入浅出地为我介绍这些知识点,并重点强调我需要注意的地方。
Goroutines
基本概念
Goroutine 是 Golang 中的轻量级线程(协程),由 Go 运行时管理。与操作系统级别的线程相比,Goroutine 的创建和管理开销更小,且内存占用更低。可以通过go
关键字启动一个 Goroutine,go
关键字的操作对象是 Golang 的函数。
一个启动以匿名函数为对象的协程的示例如下:
go func() {
fmt.Println("Hello from a goroutine")
}()
注意事项
- 并发 vs 并行:Goroutines 是并发执行的,但不一定是并行的。Golang 运行时会根据 CPU 核数来调度 Goroutines;
- Goroutine 生命周期:Goroutine 在函数返回时结束(这个函数值的是使用
go
关键字启动的函数)。如果主 Goroutine 结束(即 main 函数结束),那么所有其他 Goroutines 都将被终止。 - 资源管理:Goroutines 可能会泄露资源,尤其是在它们被阻塞或无限循环时。确保 Goroutines 能够正常退出,这样可以有效地对资源进行管理,避免资源泄露。
示例
package main
import (
"fmt"
"time"
)
func printNumbers() {
for i := 1; i <= 5; i++ {
fmt.Println(i)
time.Sleep(500 * time.Millisecond)
}
}
func main() {
go printNumbers()
go printNumbers()
time.Sleep(3 * time.Second)
}
在 Goland 当中的运行结果如下:
1
1
2
2
3
3
4
4
5
5
进程 已完成,退出代码为 0
(实际上,Golang 当中的 go 关键字我个人认为与 Python 当中的 Thread 非常的相似,Golang 当中 Goroutines 的优点可能是更加轻量和高效)
Channels
基本概念
Channel 是 Goroutines 之间通信的管道。可以通过 Channel 发送和接收数据,确保 Goroutines 之间的同步(我个人认为这是 Golang 当中最明显的优点,因为根据我非常有限的 Python 开发的经验,Python 至少不是在语言级别上支持线程之间通信的)。
可以通过如下方式创建一个 channel,使用关键字chan
:
ch := make(chan int)
注意事项
- Channel 类型:Channel 可以是带缓冲的和无缓冲的。无缓冲 Channel 是同步的,发送和接受操作都会阻塞,直到另一端准备好。带缓冲 Channel 允许在缓冲区未满时发送数据,或在缓冲区不为空时接收数据。
- 关闭 Channel:使用
close(ch)
来关闭 channel。关闭后,channel 不能再发送数据,但是仍然可以接收数据。 - 死锁:如果 Goroutines 都在等待对方发送或接收数据,那么可能会导致死锁。开发人员应确保 Channel 的使用不会导致这种情况。
示例
package main
import (
"fmt"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("worker", id, "started job", j)
time.Sleep(time.Second)
fmt.Println("worker", id, "finished job", j)
results <- j * 2
// 根据 DeepSeek 给出的上述用法
// 可以推测使用关键字 <-chan 声明的管道是发送数据的管道
// 使用 chan<- 是接收数据的管道
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= 5; j++ {
jobs <- j // 在主 goroutine 向 协程 发送数据, 通过管道 jobs 来完成
}
close(jobs)
for a := 1; a <= 5; a++ {
<-results
}
}
在 Goland 中输出的结果如下:
worker 3 started job 1
worker 1 started job 2
worker 2 started job 3
worker 2 finished job 3
worker 2 started job 4
worker 1 finished job 2
worker 1 started job 5
worker 3 finished job 1
worker 1 finished job 5
worker 2 finished job 4
进程 已完成,退出代码为 0
结合使用 Goroutines 和 Channels
基本概念
Goroutines 和 Channels 通常结合使用,以实现并发任务的协调和通信。
注意事项
- 同步:使用 Channel 来同步 Goroutines 的执行顺序;
- 数据竞争:确保多个 Goroutines 不会同时访问和修改共享数据,可以使用 Channel 或
sync
包中的同步原语(如Mutex
)来避免数据竞争。
示例
package main
import (
"fmt"
"time"
)
func producer(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
fmt.Println("Produced:", i)
time.Sleep(500 * time.Millisecond)
}
close(ch)
}
func consumer(ch <-chan int) {
for i := range ch {
fmt.Println("Consumed:", i)
time.Sleep(1 * time.Second)
}
}
func main() {
ch := make(chan int)
go producer(ch)
consumer(ch)
}
Goland 下的输出:
Produced: 0
Consumed: 0
Produced: 1
Consumed: 1
Consumed: 2
Produced: 2
Consumed: 3
Produced: 3
Consumed: 4
Produced: 4
进程 已完成,退出代码为 0
总结
- Goroutines 是 Go 并发编程的核心,轻量且高效。
- Channels 是 Goroutines 之间通信的主要方式,确保数据的安全传递;
- 注意事项:理解并发和并行的区别,避免资源泄露和死锁,确保数据同步和避免数据竞争。