首页 > 其他分享 >GO通道:无缓冲通道与缓冲通道

GO通道:无缓冲通道与缓冲通道

时间:2023-06-18 16:56:57浏览次数:34  
标签:ch 缓冲 make chan GO 写入 通道

转载请注明出处:

1.通道定义  

  在多个协程之间进行通信和管理,可以使用 Go 语言提供的通道(Channel)类型。通道是一种特殊的数据结构,可以在协程之间进行传递数据,从而实现协程之间的通信和同步。多个协程可以同时读写同一个通道,通过通道来进行数据的传递和共享。

  通道遵循先入先出(First In First Out)的原则,保证收发数据的顺序。通道是一个特殊的数据类型,在使用之前必须定义和创建通道变量,定义通道的语法如下:

 var name chan type

  语法格式说明如下:

    1)var是Go语言关键字,用于定义变量。

    2)name是通道变量名称,可自行命名。

    3)chan是Go语言关键字,将变量定义为通道类型。

    4)type是通道存放的数据类型。

  通道定义之后,还需要使用关键字make创建通道,通道的创建语法如下:

 name := make(chan type, num)

  语法格式说明如下:

    1)name是通道变量名称,可自行命名。

    2)make是Go语言关键字,用于创建通道。

    3)chan type的chan是Go语言关键字,type是通道能存放的数据类型。

    4)num是通道存放数据的数量上限。

  在实际编程中,我们直接使用关键字make创建通道即可使用,这样能省去定义通道的过程,示例代码如下:

  // 定义和创建通道
    var ch chan string
    ch = make(chan string)
    // 直接创建通道,无须定义
    ch := make(chan string)

   通道创建之后,使用通道完成写入和读取数据操作。在通道里面写入和读取数据需要由<-操作符实现,使用说明如下:

  // 构建通道
    ch := make(chan string)
    // 往通道写入数据
    ch <- "Hello"
    // 从通道获取数据,赋予变量s
    s := <- ch

 

2.无缓冲通道

  无缓冲通道是 Go 语言中一种常见的通道类型,也称为同步通道或阻塞通道。无缓冲通道的特点是在发送和接收数据时,必须有另外一个协程同时进行相反的操作,否则会阻塞当前协程。 具体来说,无缓冲通道的特点如下:

  1. 发送和接收操作是同步的,即发送操作必须等待接收操作完成后才能继续执行,接收操作也必须等待发送操作完成后才能继续执行。

  2. 无缓冲通道的容量为 0,即只有在发送和接收操作同时进行时才能传递数据,否则会阻塞当前协程。

  3. 无缓冲通道的数据传递是按照先进先出的顺序进行的,即发送的数据会按照发送的顺序被接收。 无缓冲通道可以用于协程之间的同步和通信,例如在生产者和消费者模式中,可以使用无缓冲通道来传递数据,从而保证生产者和消费者之间的同步和互斥。同时,无缓冲通道的使用也可以避免数据竞争问题,从而提高程序的安全性和可靠性。

  通道是通过关键字make创建的,在创建过程中,如果没有设置参数num,则视为创建无缓冲通道。无缓冲通道(Unbuffered Channel)是指在获取数据之前没有能力保存数据的通道,这种类型的通道要求两个Goroutine同时处于执行状态才能完成写入和获取操作。

  如果两个Goroutine没有同时准备,某一个Goroutine执行写入或获取操作将会处于阻塞等待状态,另一个Goroutine无法执行写入或获取操作,程序将会提示异常,这种类型的通道执行写入和获取的交互行为是同步,任意一个操作都无法离开另一个操作单独存在。

  当我们使用无缓冲通道的时候,必须注意通道变量的操作,确保程序中有两个或两个以上的Goroutine同时执行通道的读写操作,读写操作必须是一读一写,不能只读不写或只写不读,示例如下:

     // 只写入数据,不读取
    ch := make(chan string)
    ch <- "Tom"
    fmt.Println("wait goroutine")
    // 只读取数据,不写入
    ch := make(chan string)
    <- ch
    fmt.Println("wait goroutine")

  通道数据只写入不读取或者只读取不写入都会提示fatal error: all goroutines are asleep–deadlock异常,如果需要实现通道数据获取超时检测,可以使用关键字select实现。

  如果程序中仅有一个Goroutine,使用通道读写数据也会导致异常,比如在主函数main()中对通道写入数据,再读取通道数据,示例如下:

 package main
    import (
        "fmt"
    )
    func main() {
        // 构建通道
        ch := make(chan string)
        // 写入通道数据
        ch <- "Tom"
        // 读取通道数据
        <-ch
        fmt.Println("wait goroutine")
    }

  如果在发送和接收数据时出现异常,则会引发程序异常。例如,如果我们在发送数据之前关闭通道,则会引发一个运行时异常。为了避免这种情况的发生,我们可以使用 defer 语句在函数退出之前关闭通道。例如:

func main() {
    ch := make(chan int)
    defer close(ch) // 使用 defer 关闭通道
    go func() {
        fmt.Println("开始发送消息...")
        ch <- 1
        fmt.Println("消息发送完成。")
    }()
    fmt.Println("开始接收消息...")
    msg := <-ch
    fmt.Printf("接收到的消息是:%d\n", msg)
    fmt.Println("消息接收完成。")
}

3.带缓冲通道

  带缓冲通道(Buffered Channel)是在被获取前能存储一个或者多个数据的通道,这种类型的通道并不强制要求Goroutine之间必须同时完成写入和获取。当通道中没有数据的时候,获取动作才会阻塞;当通道没有可用缓冲区存储数据的时候,写入动作才会阻塞。

  在无缓冲通道的基础上,只要为通道增加一个有限大小的存储空间就能形成带缓冲通道。带缓冲通道在写入时无须等待获取即可再次执行下一轮写入,并且不会发生阻塞,只有当存储空间满了才会发生阻塞。同理,如果带缓冲通道中有数据,获取时将不会发生阻塞,直到通道中没有数据可读时,通道才会阻塞。

  从通道的定义角度分析,带缓冲和无缓冲通道的区别在于参数num。创建通道的时候,如果没有设置参数num,则默认参数值为0,通道为无缓冲通道,所以写入和获取数据必须同时进行才不会因阻塞而异常;如果参数num大于0,则写入和获取数据无须同步执行,因为通道有足够的空间存放数据。

  由于带缓冲通道没有读写同步限制,我们可以在同一个Goroutine中执行多次写入和获取操作,具体示例如下:

 package main
    import "fmt"
    func main() {
        // 创建一个3个元素缓冲大小的整型通道
        ch := make(chan int, 3)
        // 查看当前通道的大小
        fmt.Println(len(ch))
        // 发送3个整型元素到通道
        for i := 0; i < 3; i++ {
             ch <- i
        }
        // 查看当前通道的大小
        fmt.Println(len(ch))
        for i := 0; i < 3; i++ {
             fmt.Println(<-ch)
        }
        // 查看当前通道的大小
        fmt.Println(len(ch))
        // 查看当前通道的容量
        fmt.Println(cap(ch))
    }

  上述代码的说明如下:

    1)通过for执行了3次循环,每次循环将变量i写入通道,然后通过3次循环从通道获取数据并输出。

    2)通道写入和读取数据的时候,使用len()函数获取通道已有的数据量,判断当前通道存储的数据量是否达到上限,这样可以防止程序在运行时提示异常。

    3)使用cap()函数能获取通道的容量大小,即获取创建通道make()的参数num的大小。带缓冲通道在很多特性上和无缓冲通道类似,无缓冲通道可以看作长度为0的带缓冲通道。

  根据这个特性,带缓冲通道在下列情况下会发生阻塞:

    1)带缓冲通道的存储数据达到上限时,再次写入数据将发生阻塞而导致异常。

  2)带缓冲通道没有存储数据时,获取数据将发生阻塞而导致异常。

  Go语言为什么对通道要限制长度?因为多个Goroutine之间使用通道必然存在写入和获取操作,这种模式类型的典型例子为生产者消费者模式。如果不限制通道长度,当写入数据速度大于获取速度,内存将不断膨胀直到应用崩溃。因此,限制通道的长度有利于约束数据生产速度,生产数据量必须在数据消费速度+通道长度的范围内,这样才能正常地处理数据。

 

 

 

标签:ch,缓冲,make,chan,GO,写入,通道
From: https://www.cnblogs.com/zjdxr-up/p/17489315.html

相关文章

  • GO 协程
    转载请注明出处:线程是进程中的一个实体,被系统独立调度和分派的基本单位。线程自己不拥有系统资源,只拥有运行中必不可少的资源。同一进程中的多个线程并发执行,这些线程共享进程所拥有的资源。协程是一种比线程更加轻量级的存在,重要的是,协程不被操作系统内核管理,协程完全......
  • Python第三方模块:pymongo模块的用法
    pymongo模块是python操作mongo数据的第三方模块,记录一下常用到的简单用法。首先需要连接数据库:MongoClient():该方法第一个参数是数据库所在地址,第二个参数是数据库所在的端口号authenticate():该方法第一个参数是数据库的账号,第二个参数是数据库的密码frompymongoimpor......
  • Go 语言之 Viper 的使用
    Go语言之Viper的使用Viper介绍Viper:https://github.com/spf13/viper安装gogetgithub.com/spf13/viperViper是什么?Viper是一个针对Go应用程序的完整配置解决方案,包括12-Factor应用程序。它可以在应用程序中工作,并且可以处理所有类型的配置需求和格式。它支持:Vi......
  • UnfairSugoroku
    [ABC298E]UnfairSugoroku考虑令\(f[A][B][0/1]\)表示第一/二个人投完,一、二两人数字为\(A,B\)的概率。\[f[A][B][0]=\dfrac{1}{P}\sum_{i=1}^Pf[A-i][B][1]\]\[f[A][B][1]=\dfrac{1}{Q}\sum_{i=1}^Qf[A][B-i][0]\]复杂度\(O((N+P)(N+Q)(P+Q))\)。转移到\(A,B\)中有......
  • C++ STL(algorithm)
    1字符和整数排序#include<iostream>#include<algorithm>usingnamespacestd;voidstl1(){inta[]={-1,9,-34,100,45,2,98,32};intlen=sizeof(a)/sizeof(int);sort(a,a+len);//由小到大排列sort(a,a+len,greater<int>());//由大到小排列}vo......
  • C++ STL(algorithm)
    1字符和整数排序#include<iostream>#include<algorithm>usingnamespacestd;voidstl1(){inta[]={-1,9,-34,100,45,2,98,32};intlen=sizeof(a)/sizeof(int);sort(a,a+len);//由小到大排列sort(a,a+len,greater<int>());//由大到小排列}vo......
  • go语言tcp编程学习
    TCP编程1、网络编程分类包括TCPsocket编程(底层基于tcp/ip协议)和b/s结构的http编程(使用的是http协议,但是底层是tcpsocket实现的)资料:尚硅谷的TCP资料(3卷),可以咸鱼找下资料看下2TCPsocket编程windows查看端口密码:netstat-anb;netstat-an服务器端的处理流程监听端口;接受......
  • Go 中的格式化字符串`fmt.Sprintf()` 和 `fmt.Printf()`
    在Go中,可以使用fmt.Sprintf()和fmt.Printf()函数来格式化字符串,这两个函数类似于C语言中的scanf和printf函数。fmt.Sprintf()fmt.Sprintf()函数返回一个格式化后的字符串,而不是将其打印到标准输出流中。下面是一个例子:packagemainimport"fmt"funcmain(){......
  • go 接口循环方法
    typeCallinterface{String()string}typeCnstruct{}typeEnstruct{}funcNewcn()Call{return&Cn{}}func(c*Cn)String()string{b,_:=json.Marshal(c)fmt.Println(b)returnstring(b)}funcNewen()Call{return&En{}}func(e*E......
  • Springboot整合mongodb
    入门案例创建工程,导入依赖导入依赖点击查看代码<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId><version>2.3.9.RELEASE</version>......