协程可以让多个任务并发执行,但当多个协程需要共享数据、互相通信时,怎么保证安全呢?
Go 给出的答案就是:通道(Channel)。
一、什么是通道(Channel)?
一句话总结:通道是协程之间用来传递数据的管道。
-
协程通过通道发送和接收数据,避免了加锁的复杂性;
-
通道在设计上保证了并发安全;
-
其本质就是一种阻塞队列。
二、通道的基本语法
1. 创建通道
使用 make()
函数创建通道:
ch := make(chan int)
这里创建了一个只能传递 int
类型数据的通道。
2. 发送与接收数据
ch <- 10 // 发送数据
val := <-ch // 接收数据
注意:
-
发送操作会阻塞,直到有协程来接收;
-
接收操作也会阻塞,直到有数据可读。
三、通道使用示例
我们用两个协程演示通道的通信:
package main
import (
"fmt"
)
func sendData(ch chan int) {
ch <- 100 // 向通道发送数据
}
func main() {
ch := make(chan int)
go sendData(ch) // 启动协程
val := <-ch // 从通道接收数据
fmt.Println("Received:", val)
}
四、缓冲通道 vs 无缓冲通道
1. 无缓冲通道
ch := make(chan int)
-
发送和接收必须配对;
-
适合做同步操作;发送一个接收一个,交替使用。
2. 缓冲通道
ch := make(chan int, 3) // 缓冲区大小为3
-
发送不会立刻阻塞,直到缓冲区满;
-
适合临时存储任务、结果等。
package main import ( "fmt" ) func main() { ch := make(chan int, 2) ch <- 1 ch <- 2 fmt.Println(<-ch) // 1 fmt.Println(<-ch) // 2 }
五、使用 select 实现多路复用
select
语句可以同时监听多个通道,谁先准备好就处理谁:package main import ( "fmt" "time" ) func sendData(ch chan int, data int) { ch <- data // 向通道发送数据 } func main() { ch1 := make(chan int) ch2 := make(chan int) go sendData(ch1, 100) go sendData(ch2, 200) var forBreak = false for { select { case data := <-ch1: fmt.Println("Received from ch1:", data) case data := <-ch2: fmt.Println("Received from ch2:", data) case <-time.After(time.Second): fmt.Println("程序超时退出") forBreak = true } if forBreak { break } } fmt.Println("程序结束") }
六、通道关闭与遍历
1. 关闭通道
通道使用完毕后可关闭:
close(ch)
-
关闭后无法再发送数据;
-
关闭后的通道仍可接收数据,直到取完为止;
-
不关闭通道不会内存泄漏,除非有消费者在等待数据。
2. 遍历通道
配合 range
使用,通道关闭后遍历数据:
package main
import (
"fmt"
"sync"
)
var waitGroup sync.WaitGroup
func sendData(ch chan int, data int) {
ch <- data // 向通道发送数据
waitGroup.Done()
}
func main() {
ch1 := make(chan int, 3)
waitGroup.Add(3)
for i := 1; i <= 3; i++ {
go sendData(ch1, i)
}
waitGroup.Wait()
close(ch1)
for data := range ch1 {
fmt.Printf("读取通道信息:%d\n", data)
}
fmt.Println("程序结束")
}
通道遍历阻塞,直到通道关闭:
package main
import (
"fmt"
"sync"
)
var waitGroup sync.WaitGroup
func sendData(ch chan int, data int) {
ch <- data // 向通道发送数据
waitGroup.Done()
}
func waitClose(ch1 chan int) {
fmt.Println("等待协程完成后,关闭通道")
waitGroup.Wait()
close(ch1)
}
func main() {
ch1 := make(chan int, 3)
waitGroup.Add(3)
for i := 1; i <= 3; i++ {
go sendData(ch1, i)
}
go waitClose(ch1)
for data := range ch1 {
fmt.Printf("读取通道信息:%d\n", data)
}
fmt.Println("程序结束")
}
七、完整示例:协程+通道实现任务传递
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
}
}
func main() {
jobs := make(chan int, 5)
var wg sync.WaitGroup
// 启动3个worker协程
for w := 1; w <= 3; w++ {
wg.Add(1)
go worker(w, jobs, &wg)
}
// 发送5个任务
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
wg.Wait()
}