ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

3.10 Go之缓冲通道

2022-03-21 09:36:52  阅读:194  来源: 互联网

标签:3.10 缓冲 fmt goroutine --- runner Go 通道


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)通道?

指在被接收前能存储一个或者多个值的通道

特点:

  1. 不强制要求goroutine之间必须同时完成发送和接收

  2. 通道中没有可用缓冲区容纳发送的值时发送动作才会阻塞

  3. 通道中没有要接收的值时接收动作才会阻塞

带缓冲通道实现原理

在无缓冲通道的基础上,为通道增加一个有限大小的存储空间

无缓冲和带缓冲的区别

无缓冲通道保证数据的收发是同步的

带缓冲通道使得数据收发变成了异步的

创建带缓冲的通道

通道实例 := 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))
}

注意:

  1. len()函数刚开始并不会获取到通道缓冲区的大小,所以for循环的控制条件填len()并不能往通道中放值

  2. len()获取的是当前通道中的长度--->也就是说有值了以后才会有长度,没值没有长度

  3. 缓冲区不像数组一样,声明的大小以后len()函数可以直接获取到

带缓冲通道阻塞条件
  • 缓冲通道被填满时,再次发送数据给通道会阻塞

  • 缓冲通道为空时,尝试从通道当中接收数据

限制通道长度的原因:

  1. 防止内存崩溃

  2. 约束提供方的数据提供速度--->数据量必须在消费方处理量+通道长度的范围内,才能正常地处理数据。

  3.  

标签:3.10,缓冲,fmt,goroutine,---,runner,Go,通道
来源: https://www.cnblogs.com/JunkingBoy/p/16033291.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有