【Go语言圣经】第八节:Goroutines和Channels-DeepSeek 说 Goroutines 和 Channels

时间:2025-02-09 20:19:39

最近非常流行询问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 之间通信的主要方式,确保数据的安全传递;
  • 注意事项:理解并发和并行的区别,避免资源泄露和死锁,确保数据同步和避免数据竞争。