通道,顾名思义,使用来传递数据信号的,和队列类似,有发送端和接收端,也是先进先出的数据结构,通常是在不同的 goroutine 做通信处理,结合 select ,是 GO 并发编程中的武功秘籍
channel 有两种类型,有缓存的channel 和无缓存的channel。
无缓冲 channel 的运行时层实现不带有缓冲区,所以 Goroutine 对无缓冲 channel 的接收和发送操作是同步的。也就是说,对同一个无缓冲 channel,只有对它进行接收操作的 Goroutine 和对它进行发送操作的 Goroutine 都存在的情况下,通信才能得以进行,否则单方面的操作会让对应的 Goroutine 陷入挂起状态
var ch1 chan struct{} // nil, 不能往 nil 里面发送数据
ch2 := make(chan struct{}) // 非 nil
ch3 := make(chan struct{}, 2)
通道的发送和接收数据都是使用了 <- 符号
ch2 := make(chan struct{}, 2)
ch2 <- struct{}
ch2 <- struct{}
var temp int
temp <- ch2
<-ch2
for _,v := range ch2 {
...
}
对通道进行操作的时候,会有一些阻塞的场景。
func main() {
go func() {
ch := make(chan struct{}, 2)
ch <- struct{}{}
ch <- struct{}{}
ch <- struct{}{} // 阻塞,后面代码不会执行
fmt.Println("over")
}()
time.Sleep(time.Second * 5)
fmt.Println("all over")
}
func main() {
ch1 := make(chan int)
go func() {
fmt.Println("等待入队数据")
time.Sleep(time.Second * 3)
ch1 <- 1
fmt.Println("入队结束!")
}()
// 等待入队数据,阻塞
<-ch1
fmt.Println("over")
}
对无缓冲 channel 类型的发送与接收操作,一定要放在两个不同的 Goroutine 中进行,否则会导致 deadlock
例如:
func main() {
ch1 := make(chan int, 0)
ch1 <- 1
<-ch1
fmt.Println("over")
}
func main() {
ch := make(chan int, 5)
for i := 0; i < 5; i++ {
go func() {
i := <-ch // 阻塞
fmt.Println(i, "over!")
}()
}
ch <- 1
ch <- 2
ch <- 3
time.Sleep(time.Second * 2) // 保证前面的代码都走完了
close(ch)
time.Sleep(time.Second * 2) // 保证 close 后,剩余的 goroutine 可以监听并处理
fmt.Println("all over!")
}
func main() {
ch := make(chan int)
close(ch)
close(ch)
}
m, ok := <-ch // 当ch被关闭后,m将被赋值为ch元素类型的零值, ok值为false
for v := range ch {
// 当ch被关闭后,for range循环结束 ... ...
}
因为发送端没有像接受端那样的、可以安全判断 channel 是否被关闭了的方法, 所以一般关闭 channel 的操作都是在发送端
func main() {
ch := make(chan int)
close(ch)
ch <- 1
}
通过 select,我们可以同时在多个 channel 上进行发送 / 接收操作:
select {
case x := <-ch1: // 从channel ch1接收数据
... ...
case y, ok := <-ch2: // 从channel ch2接收数据,并根据ok值判断ch2是否已经关闭
... ...
case ch3 <- z: // 将z值发送到channel ch3中:
... ...
default: // 当上面case中的channel通信均无法实施时,执行该默认分支
}
当 select 语句中没有 default 分支,而且所有 case 中的 channel 操作都阻塞了的时候,整个 select 语句都将被阻塞,直到某一个 case 上的 channel 变成可发送,或者某个 case 上的 channel 变成可接收,select 语句才可以继续进行下去
package main
import (
"fmt"
"time"
)
type sign struct{} // 信号
func work() {
fmt.Println("working start")
time.Sleep(time.Second * 2) // 假装工作
fmt.Println("working end")
}
func process(f func()) <-chan sign {
ch := make(chan sign)
go func() { // 要单独起一个协程,通道的操作通常不能在同一个协程里
fmt.Println("process start")
f()
ch <- sign{}
fmt.Println("process end")
}()
return ch
}
func main() {
c := process(work)
<-c
}
func main() {
var wg sync.WaitGroup
ch := make(chan int)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
fmt.Println(i, "大哥我准备好了!")
wg.Done()
<-ch // 阻塞
fmt.Println(i, "干活了!")
}(i)
}
wg.Wait()
time.Sleep(time.Second * 3)
fmt.Println("兄弟们,开工了!")
close(ch) // 广播通知大家干活了
time.Sleep(time.Second * 3)
}
package main
import (
"fmt"
"sync"
)
// 利用无缓冲队列的阻塞原理,满的通道输出数据时会阻塞,通过通道的操作来控制执行
var nums int
var ch = make(chan int)
func count() {
// 单独起一个协程负责加数
go func() {
for {
ch <- nums // 满的时候会阻塞
nums++
}
}()
}
func main() {
var wg sync.WaitGroup
count()
for i := 0; i < 1000; i++ {
wg.Add(1)
go func(i int) {
<-ch
wg.Done()
}(i)
}
wg.Wait()
fmt.Println(nums)
}
package main
import (
"fmt"
"time"
)
var ch chan int
func initQueue(i int) {
ch = make(chan int, i)
}
func enqueue(i int) {
if isFull() {
return
}
ch <- i
}
func dequeue() int {
return <-ch
}
func isEmpty() bool {
if len(ch) == 0 {
return true
}
return false
}
func isFull() bool {
if len(ch) == cap(ch) {
return true
}
return false
}
func product(nums int) {
for i := 0; i < nums; i++ {
go func(i int) {
enqueue(i)
}(i)
}
}
func consumer(f func()) {
ticker := time.NewTicker(time.Second * 5) // 定时器, 5s 后超时返回
for {
select {
case v := <-ch:
fmt.Println(v)
f()
case <-ticker.C:
fmt.Println("handle over time , is over!")
return
default:
}
}
}
func main() {
nums := 10
initQueue(nums)
product(nums)
consumer(func() {
fmt.Println("go!")
})
}
var active = make(chan struct{}, 3)
var jobs = make(chan int, 10)
func main() {
go func() {
for i := 0; i < 8; i++ {
jobs <- i + 1
}
close(jobs) // 让 for 循环退出
}()
var wg sync.WaitGroup
for j := range jobs {
wg.Add(1)
go func(j int) {
active <- struct{}{} // 有三个正在消费的话,会阻塞
log.Printf("handle job: %d\n", j)
time.Sleep(2 * time.Second)
<-active // 消费完,让出坑位
wg.Done()
}(j)
}
wg.Wait()
}
当通道是非缓冲通道时, len(ch) 总是返回0,而当通道为缓冲通道时, len(ch) 返回的则是还未读取的通道元素个数, 需要注意的是,channel 原语用于多个 Goroutine 间的通信,一旦多个 Goroutine 共同对 channel 进行收发操作,len(channel) 就会在多个 Goroutine 间形成“竞态”。单纯地依靠 len(channel) 来判断 channel 中元素状态,是不能保证在后续对 channel 的收发时 channel 状态是不变的。
例如,A Goroutine 一开始判断通道不为空,准备读取数据,此时 B Goroutine 已经把数据读完了,此时 A 拿不到数据就会一直阻塞
因此,为了不阻塞在 channel 上,常见的方法是将“判空与读取”放在一个“事务”中,将“判满与写入”放在一个“事务”中
// $GOROOT/src/time/sleep.go
func sendTime(c interface{}, seq uintptr) {
// 无阻塞的向c发送当前时间
select {
case c.(chan Time) <- Now():
default:
}
}
func worker() {
select {
case <-c:
// ... do some stuff
case <-time.After(30 *time.Second):
return
}
}
func worker() {
heartbeat := time.NewTicker(30 * time.Second)
defer heartbeat.Stop()
for {
select {
case <-c:
// ... do some stuff
case <- heartbeat.C:
//... do heartbeat stuff
}
}
}
channel 分为有缓冲的和无缓冲两种,是不同的 goroutine 通信的媒介,可以实现锁机制、消息队列、广播通知,和 select 搭配也可以实现心跳机制、超时机制等,在并发编程中起到了很大的作用!