侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

go语言中channel的详细介绍

Go  /  管理员 发布于 5年前   668

本文写的是go语言中的channel,之所以写他是因为我感觉channel很重要,同时channel也是go并发的重要支撑点,因为go是使用消息传递共享内存而不是使用共享内存来通信。

并发编程是非常好的,但是并发是非常复杂的,难点在于协调,怎样处理各个程序间的通信是非常重要的。写channel的使用和特性之前我们需要回顾操作系统中的进程间的通信。

进程间的通信

在工程上一般通信模型有两种:共享数据和消息。进程通信顾名思义是指进程间的信息交换,因为进程的互斥和同步就需要进程间交换信息,学过操作系统的人都知道进程通信大致上可以分为低级进程通信和高级进程通信,现在基本上都是高级进程通信。其中高级通信机制又可以分为:消息传递系统、共享存储器系统、管道通信系统和客户机服务器系统。

1、消息传递系统

他不借助任何共享存储区或着某一种数据结构,他是以格式化的消息为单位利用系统提供的通信原语完成数据交换,感觉效率底下。
2、共享存储器系统

通信的进程共享存储区或者数据结构,进程通过这些空间进行通信,这种方式比较常见,比如某一个文件作为载体。

3、客户机服务器系统

其他几种通信机制基本上都是在同一个计算机上(可以说是同一环境),当然在一些情况下可以实现跨计算机通信。而客户机-服务器系统是不一样的,我的理解是可以当做ip请求,一个客户机请求连接到一台服务器。

这种方式在网络上是现在比较流行的,现在比较常用的远程调度,如不RPC(听着很高大上,其实在操作系统上早就有了)还有套接字、socket,这种还是比较常用的,与我们编程紧密相关的,因为你会发现好多的服务需要使用RPC调用。

4、管道通信系

最后详细说一下管道通信的机制,在操作系统级别管道是指用于链接一个读进程和一个写进程来实现他们之间通信的文件。系统上叫pipe文件。

实现的机制如:管道提供了下面的二个功能

1、互斥性,当一个进程正在对一个pipe文件执行读或者写操作时,其他的进程必须等待或阻塞或睡眠。

2、同步性,当写(输入)进程写入pipe文件后会等待或者阻塞或者睡眠,直到读(输出)进程取走数据后把他唤醒,同理,当读进程去读一个空的pipe文件时也会等待或阻塞或睡眠,直到写进程写入pipe后把他唤醒。

channel的使用

对应到go中的channel应该是第四种,go语言的channel是在语言级别提供的goroutine间通信的方式。单独说channel是没有任何意义的,因为他和goroutine一起才有效果,我们先看看一般语言解决程序间共享内存的方法。

下面是一段我们熟悉的程序:

package mainimport "fmt"var counts int = 0func Count() {    counts++    fmt.Println(counts)}func main() {    for i := 0; i < 3; i++ {        go Count()    }}

学过go的人都应该知道原因,因为:Go程序从初始化main() 方法和package,然后执行main()函数,但是当main()函数返回时,程序就会退出,主程序并不等待其他goroutine的,导致没有任何输出。

我们看看常规语言是怎样解决这种并发的问题的:

package mainimport "fmt"import "sync"import "runtime"var counts int = 0func Count(lock *sync.Mutex) {    lock.Lock()    counts++    fmt.Println(counts)    lock.Unlock()}func main() {    lock := &sync.Mutex{}    for i := 0; i < 3; i++ {        go Count(lock)    }    for {        lock.Lock()        c := counts        lock.Unlock()        runtime.Gosched()        if c >= 3 {break        }    }}

解决方式有点逗比,加了一堆的锁,因为他的执行是这样的:代码中的lock变量,每次对counts的操作,都要先将他锁住,操作完成后,再将锁打开,在主函数中,使用for循环来不断检查counter的值当然同样也要加锁。

当其值达到3时,说明所有goroutine都执行完毕了,这时主函数返回,然后程序退出。这种方式是大众语言解决并发的首选方式,可以看到为了解决并发,多写了好多的东西,如果一个初具规模的项目,不知道要加多少锁。

我们看看channel是如何解决这种问题的:

package mainimport "fmt"var counts int = 0func Count(i int, ch chan int) {    fmt.Println(i, "WriteStart")    ch <- 1    fmt.Println(i, "WriteEnd")    fmt.Println(i, "end", "and echo", i)    counts++}func main() {    chs := make([]chan int, 3)    for i := 0; i < 3; i++ {        chs[i] = make(chan int)        fmt.Println(i, "ForStart")        go Count(i, chs[i])        fmt.Println(i, "ForEnd")    }    fmt.Println("Start debug")    for num, ch := range chs {        fmt.Println(num, "ReadStart")        <-ch        fmt.Println(num, "ReadEnd")    }    fmt.Println("End")    //为了使每一步数值全部打印    for {        if counts == 3 {break        }    }}

为了看清goroutine执行的步骤和channel的特性,我特意在每一步都做了打印,下面是执行的结果,感兴趣的同学可以自己试试,打印的顺序可能不一样:

1.jpg

下面我们分析一下这个流程,看看channel在里面的作用。主程序开始:

打印 "0 ForStart 0 ForEnd" ,表示 i = 0 这个循环已经开始执行了,第一个goroutine已经开始;

打印 "1 ForStart"、"1 ForEnd"、"2 ForStart"、"2 ForEnd" 说明3次循环都开始,现在系统中存在3个goroutine;

打印 "Start debug",说明主程序继续往下走了,

打印 "0 ReadStar"t ,说明主程序执行到for循环,开始遍历chs,一开始遍历第一个,但是因为此时 i = 0 的channel为空,所以该channel的Read操作阻塞;

打印 "2 WriteStart",说明第一个 i = 2 的goroutine先执行到Count方法,准备写入channel,因为主程序读取 i = 0 的channel的操作再阻塞中,所以 i = 2的channel的读取操作没有执行,现在i = 2 的goroutine 写入channel后下面的操作阻塞;

打印 "0 WriteEnd",说明 i = 0 的goroutine也执行到Count方法,准备写入channel,此时主程序 i = 0 的channel的读取操作被唤醒;

打印 "0 WriteEnd" 和 "0 end and echo 0" 说明写入成功;

打印 "0 ReadEnd",说明唤醒的 i = 0 的channel的读取操作已经唤醒,并且读取了这个channel的数据;

打印 "0 ReadEnd",说明这个读取操作结束;

打印 "1 ReadStart",说明 i = 1 的channel读取操作开始,因为i = 1 的channel没有内容,这个读取操作只能阻塞;

打印 "1 WriteStart",说明 i = 1 的goroutine 执行到Count方法,开始写入channel 此时 i = 1的channel读取操作被唤醒;

打印 "1 WriteEnd" 和 "1 end and echo 1" 说明 i = 1 的channel写入操作完成;

打印 "1 ReadEnd",说明 i = 1 的读取操作完成;

打印 "2 ReadStart",说明 i = 2 的channel的读取操作开始,因为之前已经执行到 i = 2 的goroutine写入channel操作,只是阻塞了,现在因为读取操作的进行,i = 2的写入操作流程继续执行;

打印 "2 ReadEnd",说明 i = 2 的channel读取操作完成;

打印 "End" 说明主程序结束。

此时可能你会有疑问,i = 2 的goroutine还没有结束,主程序为啥就结束了,这正好印证了我们开始的时候说的,主程序是不等待非主程序完成的,所以按照正常的流程我们看不到 i = 2 的goroutine的的完全结束,这里为了看到他的结束我特意加了一个 counts 计算器,只有等到计算器等于3的时候才结束主程序,接着就出现了打印 "2 WriteEnd" 和 "2 end and echo 2" 到此所有的程序结束,这就是goroutine在channel作用下的执行流程。

上面分析写的的比较详细,耐心看两遍基本上就明白了,主要帮助大家理解channel的写入阻塞和读入阻塞的应用。

基本语法

channel的基本语法比较简单, 一般的声明格式是:

var ch chan ElementType

定义格式如下:

ch := make(chan int)

还有一个最常用的就是写入和读出,当你向channel写入数据时会导致程序阻塞,直到有其他goroutine从这个channel中读取数据,同理如果channel之前没有写入过数据,那么从channel中读取数据也会导致程序阻塞,直到这个channel中被写入了数据为止

ch <- value    //写入value := <-ch  //读取

关闭channel

close(ch)

判断channel是否关闭(利用多返回值的方式):

 b, status := <-ch

带缓冲的channel,说起来也容易,之前我们使用的都是不带缓冲的channel,这种方法适用于单个数据的情况,对于大量的数据不太实用,在调用make()的时候将缓冲区大小作为第二个参数传入就可以创建缓冲的channel,即使没有读取方,写入方也可以一直往channel里写入,在缓冲区被填完之前都不会阻塞。

c := make(chan int, 1024)

单项channel,单向channel只能用于写入或者读取数据。channel本身必然是同时支持读写的,否则根本没法用。所谓的单向channel概念,其实只是对channel的一种使用限制。单向channel变量的声明:

var ch1 chan int   // ch1是一个正常的channelvar ch2 <-chan int // ch2是单向channel,只用于读取int数据

单项channel的初始化

ch3 := make(chan int)ch4 := <-chan int(ch3) // ch4是一个单向的读取channel

超时机制

超时机制其实也是channel的错误处理,channel固然好用,但是有时难免会出现实用错误,当是读取channel的时候发现channel为空,如果没有错误处理,像这种情况就会使整个goroutine锁死了,无法运行。

我找了好多资料和说法,channel 并没有处理超时的方法,但是可以利用其它方法间接的处理这个问题,可以使用select机制处理,select的特点比较明显,只要有一个case完成了程序就会往下运行,利用这种方法,可以实现channel的超时处理:

原理如下:我们可以先定义一个channel,在一个方法中对这个channel进行写入操作,但是这个写入操作比较特殊,比如我们控制5s之后写入到这个channel中,这5s时间就是其他channel的超时时间,这样的话5s以后如果还有channel在执行,可以判断为超时,这是channel写入了内容,select检测到有内容就会执行这个case,然后程序就会顺利往下走了。

实现如下:

timeout := make(chan bool, 1)go func() {    time.Sleep(5s) // 等待s秒钟    timeout <- true}()select {    case <-ch:    // 从ch中读取到数据    case <-timeout:    // 没有从ch中读取到数据,但从timeout中读取到了数据}

推荐:go语言教程

以上就是go语言中channel的详细介绍的详细内容,更多请关注其它相关文章!


  • 上一条:
    go语言使用revel框架实现用户注册教程(附代码)
    下一条:
    用Go语言编写一个简单的WebSocket推送服务
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2017-09
    • 2020-03
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-12
    • 2021-01
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    • 2024-08
    • 2024-11
    • 2025-02
    • 2025-04
    • 2025-05
    • 2025-06
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客