3.10 Go之缓冲通道
缓冲通道种类
无缓冲通道
带缓冲通道
什么是无缓冲(Unbuffered Channel)通道?
指在接收前没有能力保存任何值的通道--->一进一出(只能进一个值)
特点:
要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作
理解:
可以理解为把两个goroutine和一个channel当作一个原子进行事务操作。有一方失败则全部失败
专业概念:
如果两个goroutine没有同时准备好通道将会阻塞,通道进行发送和接收的交互行为本身就是同步的.任意一个操作都无法离开另一个操作单独存在
无缓冲通道之打球
模拟的是再无缓冲状态下两个goroutine和一个channel一起协作的过程:
package main
import (
    "fmt"
    "math/rand"
    "sync"
)
/*
通过等待goroutine来判断此时有多少个goroutine协程再准备
声明一个函数,该函数进行通道相关的操作
 */
/* 声明一个goroutine组 */
var wgNo5 sync.WaitGroup
/*
玩家函数,每个玩家都有相同的操作
 */
func player(name string, court chan int) {
    // 函数退出处理
    defer wgNo5.Done()
    // 开始进行通道内操作
    for {
       // 等待数据从通道中被取出--->判断是否击球
        /*
        goroutine从通道接收数据,用来表示等待接球。
        这个接收动作会锁住goroutine,直到有数据发送到通道里。通道的接收动作返回时。--->直到最后一行代码执行
         */
        ball, ok := <-court
        /*
        检测ok标志是否为false。如果这个值是false,表示通道已经被关闭,游戏结束
         */
        if !ok {
            // 说明通道关闭,返回了false
            fmt.Printf("%s Won\n", name)
            // 返回结果
            return
        }
        // 通过随机数判断是否未从通道中取出数据
        n := rand.Intn(100)
        // 判断--->随机数摸13为0说明丢球
        if n%13 == 0 {
            fmt.Printf("%s Missed\n", name)
            // 通道关闭--->代表输了
            /*
            某个goroutine没有打中球,关闭通道。之后两个goroutine都会返回,通过defer声明的Done会被执行,程序终止
             */
            close(court)
            return
        }
        // 显示击球数,将击球数+1
        fmt.Printf("%s Hit %d\n", name, ball)
        ball++
        // 将数据放回通道--->回击
        /*
        将ball作为球重新放入通道,发送给另一位选手。在这个时刻,两个goroutine都会被锁住,直到交换完成
         */
        court <- ball
    }
}
/*
实际调用
 */
func main() {
    // 创建一个通道
    ch := make(chan int)
    // 声明计数器--->要等待几个goroutine
    wgNo5.Add(2)
    // 开启两个goroutine
    go player("Lucifer", ch)
    go player("JunkingBoy", ch)
    // 通道当中先写入一条数据作为起始
    ch <- 1
    // 等待goroutine结束
    wgNo5.Wait()
}
特点:
再通道当中的数据都是被锁住的。只有两个goroutine同时准备好执行接收和发送的操作了才会解锁
无缓冲通道之接力赛跑
接力赛跑保证的是交接棒的同步,可以抽象为通道当中的两个goroutine同步发生操作
package main
import (
    "fmt"
    "sync"
    "time"
)
/*
声明一个计时器
声明一个runner函数,再runner当中定义runner的行为,包括两个runner的协作
 */
/* 声明一个计数器 */
var wgNo6 sync.WaitGroup
func Runner(baton chan int) {
    // 声明一个runner变量
    var newRunner int
    // 将通道中的第一个runner(可能不是接力当中的第一个)赋值
    runner := <-baton
    // 环绕的跑道跑圈
    fmt.Printf("第 %d 跑步者正在跑步\n", runner)
    // 不是最后一位则创建新的跑步者
    if runner != 4 {
        newRunner = runner + 1
        fmt.Printf("第 %d 跑步者是下一位接力人!\n", newRunner)
        // 此时开启一个协程.代表下一位跑步者--->递归的用法相当于每次只要不为4就为当前goroutine构建一个接收的goroutine
        /*
        一旦接力棒传了进来,就会创建一位新跑步者,准备接力下一棒,直到 goroutine 是第四个跑步者。
        */
        go Runner(baton)
    }
    // 一圈四百米三十五秒
    time.Sleep(380 * time.Millisecond)
    // 判断是否是最后一位接力人
    if runner >= 4 {
        fmt.Printf("接力人 %d 已到终点!", runner)
        // 调用计时器的don函数--->接力完成
        wgNo6.Done()
        // 结束
        return
    }else {
        // 如果不为4则把接力棒交给下一位
        fmt.Printf("接力者 %d 交接棒给下一位接力者 %d !\n", runner, newRunner)
        // 将交接棒放回通道
        baton <- newRunner
    }
}
/* main函数进行调用 */
func main() {
    // 创建一个无缓冲通道
    ch := make(chan int)
    // 设置计时器--->为最后一位跑步者添加一位计时器--->因为无缓冲需要两个goroutine
    wgNo6.Add(1)
    // 创建第一位跑步者
    go Runner(ch)
    // 开始进行接力
    ch <- 1
    // 协程等待
    wgNo6.Wait()
}
什么是带缓冲(Buffered Channel)通道?
指在被接收前能存储一个或者多个值的通道
特点:
- 
不强制要求 goroutine之间必须同时完成发送和接收
- 
通道中没有可用缓冲区容纳发送的值时发送动作才会阻塞 
- 
通道中没有要接收的值时接收动作才会阻塞 
带缓冲通道实现原理
在无缓冲通道的基础上,为通道增加一个有限大小的存储空间
无缓冲和带缓冲的区别
无缓冲通道保证数据的收发是同步的
带缓冲通道使得数据收发变成了异步的
创建带缓冲的通道
通道实例 := make(chan 通道类型, 缓冲大小)
- 
通道类型:通道发送和接收的数据类型. 
- 
缓冲大小:通道最多可以保存的元素数量. 
- 
通道实例:被创建出的通道实例. 
示例代码:
package main
import "fmt"
func main() {
    // 创建一个可以接收三个元素缓冲大小的通道
    ch := make(chan int, 3)
    // 查看当前通道的大小
    fmt.Println(len(ch))
    // 依次向通道当中放值
    /*
    注意len获取的内容--->len获取的是当前对象的长度而不会获取到缓冲区的大小(底层上看可能连存储区域都未曾开辟.不像数组那样,数组声明的区域以后len可以获取到长度)
     */
    //var arr [3]int
    //fmt.Println(len(arr))
    for i := 0; i < 3; i++ {
       ch <- i
    }
    // 再次查看通道的大小
    fmt.Println(len(ch))
}
注意:
- 
len()函数刚开始并不会获取到通道缓冲区的大小,所以for循环的控制条件填len()并不能往通道中放值
- 
len()获取的是当前通道中的长度--->也就是说有值了以后才会有长度,没值没有长度
- 
缓冲区不像数组一样,声明的大小以后 len()函数可以直接获取到
带缓冲通道阻塞条件
- 
缓冲通道被填满时,再次发送数据给通道会阻塞 
- 
缓冲通道为空时,尝试从通道当中接收数据 
限制通道长度的原因:
- 
防止内存崩溃 
- 
约束提供方的数据提供速度--->数据量必须在消费方处理量+通道长度的范围内,才能正常地处理数据。 
- 
 
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号