一、 chan 的类型
channel类型是可以带有方向的,假设T是一种类型:
- chan T是双向channel类型,编译器允许对双向channel同时进行发送和接收。
- chan<- T是只写channel类型,编译器只允许往channel里面发送数据。
- <-chan T是只读channel类型,编辑器只允许从channel里面接收数据。
双向类型的channel,可以被强制转换成只读channel或者是只写channel,但是反过来却不行,只读和只写channel是不可以转换成双向channel的。
1 2 3 4 5 6 7 8 9 10 11 |
var o <-chan int var i chan<- int var c chan int o = (<-chan int)(c) // ok i = (chan<- int)(c) // ok c = (chan int)(o) // 报错 c = (chan int)(i) // 报错 |
二、 chan 的初始化
channel里面的value buffer的容量也就是channel的容量。channel的容量为零表示这是一个阻塞型通道,非零表示缓冲型通道[非阻塞型通道]。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# 未分配内存 var ch chan int // nil channel // 这样操作会报错 go func() { ch <- 1 }() fmt.Println(<-ch) # 无缓冲 chan ch := make(chan string) // zero channel go func() { fmt.Println("goroutine 1") ch <- 1 // 阻塞 fmt.Println("goroutine 2") }() fmt.Println("master 1") fmt.Println(<-ch) fmt.Println("master 2") # 有缓冲 chan ch := make(chan string, 1) // buffered channel go func() { fmt.Println("goroutine 1") ch <- 1 // 不阻塞 fmt.Println("goroutine 2") }() fmt.Println("master 1") fmt.Println(<-ch) fmt.Println("master 2") |
三、内部结构
每个channel内部实现都有三个队列
- 接收消息的协程队列。这个队列的结构是一个限定最大长度的链表,所有阻塞在channel的接收操作的协程都会被放在这个队列里。
- 发送消息的协程队列。这个队列的结构也是一个限定最大长度的链表。所有阻塞在channel的发送操作的协程也都会被放在这个队列里。
- 环形数据缓冲队列。这个环形数组的大小就是channel的容量。如果数组装满了,就表示channel满了,如果数组里一个值也没有,就表示channel是空的。对于一个阻塞型channel来说,它总是同时处于即满又空的状态。
四、相关操作
1 2 3 4 5 6 7 8 9 |
<br /> ch := make(chan int, 10) fmt.Println(cap(ch)) // 查看容量 ch <- 1 // 入chan fmt.Println(len(ch)) // 查看数据长度 i := <-ch // 出chan fmt.Println(i) close(ch) // 关闭chan |
五、select-case
1 2 3 4 5 6 7 8 9 10 11 |
select { case v1 := <-c1: fmt.Printf("received %v from c1\n", v1) case v2 := <-c2: fmt.Printf("received %v from c2\n", v1) case c3 <- 23: fmt.Printf("sent %v to c3\n", 23) default: fmt.Printf("no one was ready to communicate\n") } |
上面这段代码中,select 语句有四个 case 子语句,前两个是 receive 操作,第三个是 send 操作,最后一个是默认操作。代码执行到 select 时,case 语句会按照源代码的顺序被评估,且只评估一次,评估的结果会出现下面这几种情况:
- 除 default 外,如果只有一个 case 语句评估通过,那么就执行这个case里的语句;
- 除 default 外,如果有多个 case 语句评估通过,那么通过伪随机的方式随机选一个;
- 如果 default 外的 case 语句都没有通过评估,那么执行 default 里的语句;
- 如果没有 default,那么 代码块会被阻塞,指导有一个 case 通过评估;否则一直阻塞
下面会随机输出 v1, v2
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var c1 chan int = make(chan int, 1) var c2 chan int = make(chan int, 1) c1 <- 1 c2 <- 2 select { case v1 := <-c1: fmt.Println("v1:", v1) case v2 := <-c2: fmt.Println("v2:", v2) } |
下面会循环输出全部,直至主进程销毁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var c1 chan int = make(chan int, 1) var c2 chan int = make(chan int, 1) c1 <- 1 c2 <- 2 go func() { for { select { case v1 := <-c1: fmt.Println("v1:", v1) case v2 := <-c2: fmt.Println("v2:", v2) } } }() time.Sleep(10 * time.Second) |
超时设定
1 2 3 4 5 6 7 8 9 10 11 12 |
c := make(chan int, 1) timeout := time.After(5 * time.Second) for { select { case s := <-c: fmt.Println(s) case <-timeout: fmt.Println("You talk too much.") return } } |