1.channel简介
Channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication)。
它的操作符是箭头 <- 。用来协程间传递数据。
ch <- v // 发送值v到Channel ch中
v := <-ch // 从Channel ch中接收数据,并将数据赋值给v
ch := make(chan int, 100) //容量(capacity)代表Channel容纳的最多的元素的数量,代表Channel的缓存的大小。
如果没有设置容量,或者容量设置为0, 说明Channel没有缓存,只有sender和receiver都准备好了后它们的通讯(communication)才会发生(Blocking)。如果设置了缓存,就有可能不发生阻塞, 只有buffer满了后 send才会阻塞, 而只有缓存空了后receive才会阻塞。一个nil channel不会通信。
close(ch) //关闭channel
无缓冲阻塞场景:1.没有协程在写却读 2.没有协程在读却写
有缓冲阻塞场景:1.缓冲中无数据却读 2.缓冲已满却写
无缓冲的channel,不管是入还是出,都会阻塞,所以在同一个goroutine中,不能同时对同一个无缓冲channel进行入和出操作;
带缓冲的channel,在队列满之前,不会阻塞;队列满之后,依然会阻塞。
2.应用1:作为一个FIFO队列
package main
//用channel实现队列,查看是否原子性
import (
"errors"
"fmt"
"sync"
)
func main(){
ch := make(chan int, 20)
fmt.Println(len(ch))
fmt.Println(cap(ch))
for i:=0; i<20; i++{
ch <- i+1
}
wg := sync.WaitGroup{}
wg.Add(30)
var err error
for j:=0; j<30; j++{
go get(&ch, &wg, &err)
}
wg.Wait()
}
func get(ch *chan int, wg *sync.WaitGroup, err *error){
defer wg.Done()
select {
case x := <- *ch: //如果可以读出,就读
fmt.Printf("get number: %d, channel size: %d\n", x, len(*ch))
return
default: //失败返回
fmt.Println("no number")
*err = errors.New("channel has no data")
return
}
}
3.应用2:实现类似sync.WaitGroup的同步
package main
import (
"fmt"
"time"
)
func main() {
//用sleep实现定时器
fmt.Println(time.Now())
time.Sleep(time.Second)
fmt.Println(time.Now())
//用timer实现定时器
timer := time.NewTimer(time.Second)
fmt.Println(<-timer.C)
//用after实现定时器
fmt.Println(<-time.After(time.Second))
//周期定时
tiker := time.NewTicker(time.Second)
for i :=1; i<4; i++{
fmt.Println(<-tiker.C)
}
fmt.Println("------------------")
//定时完成的操作写在协程里
ticker := time.NewTicker(time.Second * 3)
ch := make(chan int) //该channel完成同步,实现下面的协程执行完主线程才结束
go func() {
var x int
for x < 10 {
select {
case <-ticker.C:
x++
fmt.Printf("%d\n", x)
fmt.Println(time.Now())
time.Sleep(time.Second * 1) //case里的操作不要超过定时时间,不然会不符合预期
}
}
ticker.Stop()
ch <- 0
}()
<-ch
}
//这里的channel 实现了一个 sync.WaitGroup的效果,主进程直接到<-ch,无拥塞channel,里面没东西,直接拿是会阻塞的,直到里面有东西
//也就是上面协程里的10次定时都结束,往channel写一个东西,就终止阻塞,使程序结束
// ch := make(chan int) 和 ch := make(chan int, 1) 理解:
// 有1的缓冲channel,一个协程写入拿到这个数,其他再拿都拿不到,配合select,拿不到就走,那个协程执行完操作之后,再往里写个数,其他协程就可以拿了。
//其实不管是有缓冲还是无缓冲,单纯用都会阻塞,无非是有一个buffer
//但是,加上select,都可以实现不拥塞,直接过。
4.应用3:搭配select实现锁效果
mutex实现的锁:拥塞的,一个获锁,其他需要一直等待,直到上一个操作释放,再执行操作
channel实现的锁:可以选择拥塞不拥塞,得不到可以直接放弃,也可以等待一段时间执行其他操作。
搭配select 实现的是io操作。
package main
//实现初始化一台实例的锁,不同appid都可以更新map
//实现channle的拥塞锁
import (
"errors"
"fmt"
"sync"
"time"
)
func main(){
ch := make(chan int, 1)
ch <- 1
wg := sync.WaitGroup{}
var err error
wg.Add(10)
for i:=0; i<10; i++{
go get_blockf(&ch, &wg, &err) //一直拥塞
}
wg.Wait()
wg.Add(10)
for i:=0; i<10; i++{
go get_block(&ch, &wg, &err) //拥塞2s
}
wg.Wait()
wg.Add(10)
for i:=0; i<10; i++{
go get_noblock(&ch, &wg, &err) //无拥塞
}
wg.Wait()
}
func get_blockf(ch *chan int, wg *sync.WaitGroup, err *error){
defer wg.Done()
select {
case x := <- *ch: //如果可以读出,就读
fmt.Printf("Block get number: %d, channel size: %d\n", x, len(*ch))
time.Sleep(time.Second*1)
*ch <-x+1
}
return
}
func get_block(ch *chan int, wg *sync.WaitGroup, err *error){
defer wg.Done()
select {
case x := <- *ch: //如果可以读出,就读
fmt.Printf("Block 2s get number: %d, channel size: %d\n", x, len(*ch))
time.Sleep(time.Second*1)
*ch <-x+1
case <- time.After(time.Second *20): //等待过程中一直尝试,尝试成功即从ch取数,超时还取不到执行下面操作
fmt.Println("no number")
*err = errors.New("channel has no data")
}
return
}
func get_noblock(ch *chan int, wg *sync.WaitGroup, err *error){
defer wg.Done()
select {
case x := <- *ch: //如果可以读出,就读
fmt.Printf("Block 2s get number: %d, channel size: %d\n", x, len(*ch))
time.Sleep(time.Second*1)
*ch <-x+1
default: //等待过程中一直尝试,尝试成功即从ch取数,超时还取不到执行下面操作
fmt.Println("no number")
*err = errors.New("channel has no data")
}
return
}