Go 中的 Channel
核心:进程内的通信管道, 用于进程内协程的数据传递。
记住这句话,全文都是围绕这个展开的。进程通信有多种方式,channel是其中一种,其无疑是Go语言进程内通信的利器。
作用
Channel 同时也是 Go 语言中的一种数据结构,用于在不同的 goroutine 之间传递数据。
它提供了一种通信机制,使得 goroutine 可以安全地交换数据,而不需要显式的锁或其他同步机制。
原理
Channel 是类型化的管道,通过它可以发送和接收特定类型的值。Channel 的操作是阻塞的:
- 发送操作:当一个 goroutine 向 channel 发送数据时,如果没有其他 goroutine 正在等待接收数据,那么发送操作会阻塞,直到有 goroutine 接收数据。
package main
import (
"fmt"
)
func main() {
// 创建一个整型的 channel
ch := make(chan int)
// 启动一个 goroutine
go func() {
// 向 channel 发送数据
ch <- 42
fmt.Print("Sent Done")
}()
// 这里如果没有接收,则 ch <- 42会阻塞,也就不会打印 Sent Done
value := <-ch
fmt.Println(value)
fmt.Println("Done")
}
- 接收操作:当一个 goroutine 从 channel 接收数据时,如果没有其他 goroutine 正在发送数据,那么接收操作会阻塞,直到有 goroutine 发送数据。
package main
import (
"fmt"
)
func main() {
// 创建一个整型的 channel
ch := make(chan int)
// 启动一个 goroutine
// go func() {
// // 向 channel 发送数据
// ch <- 42
// fmt.Print("Sent Done")
// }()
// 还是这段代码 ,把发送这部分 注释掉,则接收操作这里会阻塞住
// 同时由于永远无法结束,
// 会报错误: fatal error: all goroutines are asleep - deadlock!
value := <-ch
fmt.Println(value)
fmt.Println("Done")
}
使用场景
- Goroutine 之间的通信:在并发编程中,channel 用于在多个 goroutine 之间传递数据。
- 同步:通过阻塞和非阻塞的特性,channel 可以用来同步多个 goroutine 的执行顺序。
示例
基本使用
package main
import (
"fmt"
)
func main() {
// 创建一个整型的 channel
ch := make(chan int)
// 启动一个 goroutine
go func() {
// 向 channel 发送数据
ch <- 42
}()
// 从 channel 接收数据
value := <-ch
fmt.Println(value) // 输出: 42
}
带缓冲的 Channel
package main
import (
"fmt"
)
func main() {
// 创建一个带缓冲的 channel,容量为2
ch := make(chan int, 2)
// 向 channel 发送数据
ch <- 1
ch <- 2
// 从 channel 接收数据
fmt.Println(<-ch) // 输出: 1
fmt.Println(<-ch) // 输出: 2
}
使用 select
语句
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "two"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println("Received", msg1)
case msg2 := <-ch2:
fmt.Println("Received", msg2)
}
}
}
任务分发和结果收集
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
fmt.Printf("Worker %d processing job %d\\n", id, job)
results <- job * 2
}
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
for w := 1; w <= 3; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
wg.Wait()
close(results)
for result := range results {
fmt.Println("Result:", result)
}
}
在这个示例中,我们创建了一个任务分发和结果收集的系统,使用 channel 在主 goroutine 和 worker goroutine 之间传递任务和结果。这里多说下任务分配机制,
任务分配机制
当 jobs <- j
执行时,哪个 worker 会被触发并处理任务是由 Go 的调度器决定的。由于所有 worker 都在等待从 jobs
通道接收任务,调度器会随机选择一个处于等待状态的 worker 来处理任务。
上述代码
- 代码启动了 3 个 worker,它们并发地从同一个
jobs
通道接收任务。 - 当
jobs <- j
执行时,哪个 worker 会被触发是由 Go 的调度器随机决定的。 - 每个 worker 处理任务后,将结果发送到
results
通道。 - 主 goroutine 等待所有 worker 完成任务,并从
results
通道接收并打印结果。
关闭通道
package main
import (
"fmt"
)
func main() {
// 创建一个整型的 channel
ch := make(chan int)
// 启动一个 goroutine,向 channel 发送数据
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
// 关闭 channel
close(ch)
}()
// 从 channel 接收数据,直到 channel 关闭
for value := range ch {
fmt.Println(value)
}
fmt.Println("Channel closed, done receiving.")
}
在 Go 中,close
函数用于关闭一个 channel。关闭 channel 有以下几个作用:
- 通知接收者:关闭 channel 后,接收者可以检测到 channel 已经关闭,从而停止接收数据。
- 防止发送数据:关闭 channel 后,不能再向该 channel 发送数据,否则会引发 panic。
总结
- Channel 是 Go 语言中的一种数据结构,用于在 goroutine 之间传递数据。
- 作用:用于通信、同步和资源共享。
- 原理:通过阻塞和非阻塞机制实现数据传递。
- 使用场景:任务分发、结果收集、同步等。