Channels 通道
不要通过共享内存进行通信;相反,通过通信共享内存。
通道是一种强大的并发原语,允许在 goroutine 之间进行通信和同步。
你可以使用 make
函数创建一个通道。
默认情况下,通道是无缓冲的。这意味着它只会在有一个相应的接收 (<- chan
) 准备好接收值的情况下才会允许发送 (chan <-
)。
ch := make(chan int) // 创建一个类型为int的无缓冲通道
ch2 := make(chan int, 3) // 带有容量为3的缓冲通道
缓冲
缓冲通道可在没有接收器的情况下发送一定数量的值。
package main
import "fmt"
func main() {
messages := make(chan string, 2)
messages <- "lite"
messages <- "rank"
fmt.Println(<-messages)
fmt.Println(<-messages)
}
同步
package main
import (
"fmt"
"time"
)
func worker(done chan int) {
fmt.Print("working...")
time.Sleep(time.Second)
fmt.Println("done")
done <- 1
}
func main() {
done := make(chan int, 1)
go worker(done)
<-done
}
worker
函数模拟一些工作,并将值发送到 done
通道来通知工作完成。
方向
通道可以有方向,这意味着开发者可以指定通道是仅用于发送还是仅用于接收值。
这是一种在语言层级防止通道误用的类型检查形式。通道方向使用 <-
操作符声明。
只发送通道
package main
import "fmt"
func sendData(sendCh chan<- int) {
sendCh <- 42
}
func main() {
ch := make(chan int)
go sendData(ch)
fmt.Println("Data sent successfully")
}
只接收通道
package main
import "fmt"
func receiveData(receiveCh <-chan int) {
data := <-receiveCh
fmt.Println("Received data:", data)
}
func main() {
ch := make(chan int)
go receiveData(ch)
fmt.Println("Data received successfully")
}
关闭
你可以关闭通道以表示不会再发送任何值。关闭通道可通知接收方停止等待新值。
package main
import (
"fmt"
"time"
)
func produceData(dataCh chan<- int) {
for i := 1; i <= 5; i++ {
dataCh <- i
}
close(dataCh)
}
func consumeData(dataCh <-chan int, done chan<- struct{}) {
for {
// 如果通道已关闭,则'ok'值将为false。
data, ok := <-dataCh
if !ok {
fmt.Println("Channel closed. No more data.")
close(done) // 通知消费完成
return
}
fmt.Println("Received data:", data)
time.Sleep(time.Second)
}
}
func main() {
dataCh := make(chan int)
done := make(chan struct{})
go produceData(dataCh)
go consumeData(dataCh, done)
// 阻塞,直到'done'通道关闭,表示消费完成。
<-done
fmt.Println("Main goroutine exiting.")
}
迭代
for ... range
循环通常与通道一起使用,以在通道关闭之前迭代值。
package main
import (
"fmt"
"time"
)
func produceData(dataCh chan<- int) {
for i := 1; i <= 5; i++ {
dataCh <- i
}
close(dataCh)
}
func consumeData(dataCh <-chan int, done chan<- struct{}) {
for data := range dataCh {
fmt.Println("Received data:", data)
time.Sleep(time.Second) // 模拟一些处理
}
// 通道已关闭,通知消费完成
close(done)
}
func main() {
dataCh := make(chan int)
done := make(chan struct{})
go produceData(dataCh)
go consumeData(dataCh, done)
// 阻塞,直到'done'通道关闭,表示消费完成。
<-done
fmt.Println("Main goroutine exiting.")
}
非阻塞操作
你可以使用 select
语句在通道上执行非阻塞操作。
select
语句允许你同时等待多个通信操作,然后选择执行第一个可以执行的操作。
package main
import "fmt"
func main() {
dataCh := make(chan int, 1)
dataCh <- 58
// 尝试在不阻塞的情况下向通道发送值。
select {
case dataCh <- 42:
fmt.Println("Data sent successfully.")
default:
fmt.Println("Unable to send data. Channel is full.")
}
// => Unable to send data. Channel is full.
// 尝试在不阻塞的情况下从通道接收值。
select {
case data := <-dataCh:
fmt.Println("Received data:", data)
default:
fmt.Println("No data available. Channel is empty.")
}
// => Received data: 58
close(dataCh)
}
协程池
协程池是一种常见的并发模式,其使用固定数量的工作 goroutine 来处理来自队列的任务。 通道通常用于主 goroutine(生成任务的地方)和工作 goroutine 之间通信。
package main
import "fmt"
func worker(id int, jobs <-chan int, results chan<- int, done chan<- struct{}) {
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
results <- job * 2
}
// 通知已完成其工作
done <- struct{}{}
}
func main() {
const numJobs = 10
const numWorkers = 3
// 为主goroutine和工作goroutine之间的通信创建通道。
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
done := make(chan struct{}, numWorkers)
for i := 1; i <= numWorkers; i++ {
go worker(i, jobs, results, done)
}
// 生成任务并将它们发送到jobs通道。
go func() {
for i := 1; i <= numJobs; i++ {
jobs <- i
}
close(jobs)
}()
// 等待所有工作goroutine完成。
for i := 1; i <= numWorkers; i++ {
<-done
}
close(results)
for result := range results {
fmt.Printf("Result: %d\n", result)
}
}
代码挑战
写一个 Go 程序,以并发方式计算字符串中单词的出现次数。该程序应使用 goroutine,并使用通道来传递结果。
Loading...
> 此处输出代码运行结果