channel是goroutine之间通信的管道,可以将值从一个goroutine发送到channel,另一个goroutine从channel接收到这些值。
Do not communicate by sharing memory; instead, share memory by communicating.
创建channel
go
//无缓存channel
ch := make(chan int)
//容量为10的缓冲channel
ch := make(chan int, 10)
//指向文件的指针的缓冲通道
cs := make(chan *os.File, 100)
//单向channel
sendCh := make(chan<- int) // 只能发送的通道
recvCh := make(<-chan int) // 只能接收的通道
len(ch) //返回通道当前长度
cap(ch) // 返回通道缓冲区容量
ch <- v //往通道发送v
v := <-ch //从通道ch接受消息
//关闭通道
close(ch)
通道其中常见的一种用法
go
c := make(chan int) // Allocate a channel.
// Start the sort in a goroutine; when it completes, signal on the channel.
go func() {
list.Sort()
c <- 1 // Send a signal; value does not matter.
}()
doSomethingForAWhile()
<-c // Wait for sort to finish; discard sent value.
通道(channel)可以包含任何类型的数据,包括其他通道(channels of channels)这种能力允许你创建复杂的并发模式和数据结构。
默认情况下,发送和接收会阻塞,直到另一端准备就绪。这允许 goroutine 在没有显式锁或条件变量的情况下进行同步。
使用 for range遍历通道
for range循环可以持续的从通道中接收数据。
- 如果通道没有被关闭,且通道中的数据已经被接收完了,for range会一直阻塞等待新的数据发送到通道。
- 如果通道被关闭,for range循环会继续消费通道中剩余的数据,然后退出循环。
js
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
使用select处理多个通道
select会阻塞直到某个分支可以执行为止,如果多个分支都可以执行,随机选择一个执行。
如果不想阻塞,可以添加default子句,如果所有相关的通道都没有准备好,select会执行default子句的代码。
每次select只会消费掉一个值,如果希望处理通道中所有的值,需要在循环中使用select.
go
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
关闭channel
close(ch) 关闭通道
关闭通道后,接收方会收到一个信号,表示不会再有新数据到达。
从已关闭的通道读取数据时,会返回通道中的所有剩余数据,直到通道为空。
通常只有通道的发送者(写入数据的一方)应该关闭通道,因为发送者知道何时完成发送操作。
检查通道是否关闭
go
v, ok := <-ch
if !ok {
// 通道已关闭
}
当尝试从已关闭的通道接收数据时,接收操作将成功,但返回值为通道的零值,返回布尔值为false
go
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go func() {
ch <- "消息"
close(ch) // 关闭通道
}()
for {
select {
case msg, ok := <-ch: // ok 是一个布尔值
if ok {
fmt.Println("接收到:", msg)
} else {
fmt.Println(msg)
fmt.Println("通道已关闭,未接收到新消息")
}
default:
fmt.Println("没有可用的通道")
}
time.Sleep(3 * time.Second)
}
}
单向通道
单向通道允许通道被限制为只能发送数据或接收数据。可以使代码清晰、避免错误、提高安全性。
- chan<- T 定义只能往该通道发送数据
- <-chan T 定义只能从该通道读取数据,只能接收的通道不能用于close操作,close操作是发送操作的一种,用于告诉通道接受者不再有数据发送到通道。
双向通道类型的值chan T可以隐式转换为仅发送类型chan<- T和仅接受类型<-chan T,但不能反过来
go
package main
import "fmt"
// 定义一个只能发送的字符串通道pings,将msg发送到pings通道
func ping(pings chan<- string, msg string) {
pings <- msg
}
// 接收两个参数,一个只能接收的字符串通道 `pings`,和一个只能发送的字符串通道 `pongs`
func pong(pings <-chan string, pongs chan<- string) {
msg := <-pings
pongs <- msg
}
func main() {
pings := make(chan string, 1)
pongs := make(chan string, 1)
ping(pings, "passed message")
pong(pings, pongs)
fmt.Println(<-pongs)
}
通道信号量的例子
通道可以像信号量一样使用,例如用于限制吞吐量。
go
package main
import (
"fmt"
"time"
)
var sem = make(chan int, 10)
func Server() {
for i := 0; i < 100; i++ {
sem <- 1
go func() {
// 模拟业务处理
fmt.Printf("业务处理中%v \n", i)
time.Sleep(10 * time.Second)
<-sem
}()
}
}
func main() {
Server()
}
channel结构
go
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // 通道缓冲区大小
buf unsafe.Pointer // 指向数据元素数组的指针,实际存储发送到通道中的数据。使用
elemsize uint16 //每个元素的大小,根据元素类型决定
closed uint32 //通道是否关闭标识
elemtype *_type // element type 道上发送元素的类型
sendx uint // send index 用于指向通道缓冲区中下一个可用的发送位置
recvx uint // receive index 用于指向通道缓冲区中下一个可接收的元素
recvq waitq // 用于存储等待接收数据的 goroutine 的队列。当没有数据可供接收时,接收方会被阻塞并加入此队列。
sendq waitq //用于存储等待发送数据的 goroutine 的队列。当缓冲区满时,发送方会被阻塞并加入此队列。
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex //互斥锁,用于保护通道的并发访问,确保在多个 goroutine 访问通道时的安全性,防止数据竞争。
}