go语言并发编程(四) ——再探管道

简介: go语言并发编程(四) ——再探管道

单向管道

什么是单向管道

在Go语言中,管道有两种类型:双向管道与单向管道.双向管道指的是可以读也可以写,能在管道两边进行数据的读写操作,而单向管道指的是只能在管道的一边进行操作,我们手动创建一个只读/写的管道意义不大,一般是用于函数的参数传递或是作为返回值出现,例如我们用来关闭协程的管道的函数:

func close(c <- chan T

双向管道可以转换为单向管道,反过来则不可以。通常情况下,将双向管道传给某个协程或函数并且不希望它读取/发送数据,就可以用到单向管道来限制另一方的行为。

func main() {
ch := make(chan int, 1)
go write(ch)
fmt.Println(<-ch)
}
func write(ch chan<- int) {
// 只能对管道发送数据
ch <- 1
}

当然读管道同理

for range 遍历管道

在Go语言中我们可以基于for range来遍历读取管道中的数据,比如下面的这个例子:

package main
import "fmt"
func main() {
  ch := make(chan int, 10)
  defer close(ch)
  for i := 0; i < 10; i++ {
    ch <- i
  }
  for i := 0; i < 10; i++ {
    fmt.Println(<-ch)
  }
}

输出为:

0
1
2
3
4
5
6
7
8
9

其实通常来说当我们使用 for range来遍历数据结构的时候,一般会有两个返回值:一个是索引,另一个则是该索引对应的位置的元素值,但是对于管道而言,有且仅有一个返回值,就是缓冲区的元素值,for range会遍历读取管道缓冲区中的元素,当管道缓冲区为空时,就会阻塞等待,直到有其他协程向管道中写入数据才会继续读取数据。

注意:关闭管道的操作尽量在发送数据的那一方进行,而不要在接收方关闭管道,因为大多数情况下接收方只知道接收数据,并不知道该在什么时候关闭管道。

select

什么是select

select在Linux系统中一般用于IO多路复用,类似的,在Go语言中,select是一种管道多路复用的控制结构,而到底什么是多路复用,简单的用一句话来概括的话就是:在某一时刻,同时监测多个元素是否可用,而被监测的可以是网络请求,文件IO等,而在Go语言中的select则用于检测管道是否可用,如果可用,则读取管道中的数据,否则阻塞等待。

接下来我们来看一个简单的select的例子:

package main
import "fmt"
func main() {
  ch1, ch2, ch3 := make(chan bool), make(chan bool), make(chan bool)
  defer func() {
    close(ch1)
    close(ch2)
    close(ch3)
  }()
  select {
  case n, ok := <-ch1:
    fmt.Println(n, ok)
  case n, ok := <-ch2:
    fmt.Println(n, ok)
  case n, ok := <-ch3:
    fmt.Println(n, ok)
  default:
    fmt.Println("default")
  }
}

与switch相似,select由多个case和一个default组成,default分支可以忽略,而每一个case只能操作一种管道,并且只能进行一种操作,要么读要么写,当有多个case可用的时候,select会随机选择一个case来执行,如果所有case都不可用,就会执行default分支,倘若没有default分支,将会阻塞等待,直到至少有一个case可用。所以上面的输出结果

为:

default

不过上述的的例子中,执行完对应分支以后,主协程就直接退出了,所以如果我们想一直检测管道的话,要给select语句加上一个死循环代码来保证select可以一直监测管道:

package main
import "fmt"
func main() {
  ch1, ch2, ch3 := make(chan int), make(chan int), make(chan int)
  defer func() {
    close(ch1)
    close(ch2)
    close(ch3)
  }()
  go Send(ch1)
  go Send(ch2)
  go Send(ch3)
  for {
    select {
    case n, ok := <-ch1:
      fmt.Println(n, ok)
    case n, ok := <-ch2:
      fmt.Println(n, ok)
    case n, ok := <-ch3:
      fmt.Println(n, ok)
    }
  }
}
func Send(ch chan<- int) {
  for i := 0; i < 3; i++ {
    ch <- i
  }
}

这样确实三个管道都能用上了,但是死循环+select会导致主协程永久阻塞,所以可以将其单独放到新协程中,并且加上一些其他的逻辑。

package main
import (
  "fmt"
  "time"
)
func main() {
  chan1, chan2, chan3 := make(chan int), make(chan int), make(chan int)
  l := make(chan struct{})
  defer func() {
    close(chan1)
    close(chan2)
    close(chan3)
  }()
  go Send(chan1)
  go Send(chan2)
  go Send(chan3)
  go func() {
  Loop:
    for {
      select {
      case n, ok := <-chan1:
        fmt.Println("A", n, ok)
      case n, ok := <-chan2:
        fmt.Println("B", n, ok)
      case n, ok := <-chan3:
        fmt.Println("C", n, ok)
      case <-time.After(1 * time.Second): //设置超时时间
        break Loop //
      }
    }
    l <- struct{}{}
  }()
  <-l
}
func Send(ch chan<- int) {
  for i := 0; i < 3; i++ {
    ch <- i
  }
}

上例中通过for循环配合select来一直监测三个管道是否可以用,并且第四个case是一个超时管道,超时过后便会退出循环,结束子协程。最终输出如下:

输出结果为:

A 0 true
B 0 true
B 1 true
A 1 true
A 2 true
C 0 true
B 2 true
C 1 true
C 2 true

超时机制

在是一个例子中我们用到了time.After函数,这个函数的返回值是一个只读的管道,我们可以利用它来实现一个比较简单的超时机制,比如下面这个例子:

package main
import "time"
func main() {
  ch := make(chan struct{}, 1)
  go func() {
    time.Sleep(time.Second * 2)
    ch <- struct{}{}
  }()
Loop:
  for {
    select {
    case <-ch:
      println("ok")
    case <-time.After(time.Second):
      println("超时")
      break Loop
    }
  }
}

输出为:

超时

注意:在select的case中对值为nil的管道进行操作的话,并不会导致阻塞,该case则会被忽略,永远也不会被执行

文章知识点与官方知识档案

相关文章
|
15天前
|
存储 监控 算法
员工上网行为监控中的Go语言算法:布隆过滤器的应用
在信息化高速发展的时代,企业上网行为监管至关重要。布隆过滤器作为一种高效、节省空间的概率性数据结构,适用于大规模URL查询与匹配,是实现精准上网行为管理的理想选择。本文探讨了布隆过滤器的原理及其优缺点,并展示了如何使用Go语言实现该算法,以提升企业网络管理效率和安全性。尽管存在误报等局限性,但合理配置下,布隆过滤器为企业提供了经济有效的解决方案。
58 8
员工上网行为监控中的Go语言算法:布隆过滤器的应用
|
1月前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
1月前
|
程序员 Go
go语言中结构体(Struct)
go语言中结构体(Struct)
104 71
|
1月前
|
存储 Go 索引
go语言中的数组(Array)
go语言中的数组(Array)
107 67
|
10天前
|
算法 安全 Go
Go 语言中实现 RSA 加解密、签名验证算法
随着互联网的发展,安全需求日益增长。非对称加密算法RSA成为密码学中的重要代表。本文介绍如何使用Go语言和[forgoer/openssl](https://github.com/forgoer/openssl)库简化RSA加解密操作,包括秘钥生成、加解密及签名验证。该库还支持AES、DES等常用算法,安装简便,代码示例清晰易懂。
46 12
|
13天前
|
监控 算法 安全
解锁企业计算机监控的关键:基于 Go 语言的精准洞察算法
企业计算机监控在数字化浪潮下至关重要,旨在保障信息资产安全与高效运营。利用Go语言的并发编程和系统交互能力,通过进程监控、网络行为分析及应用程序使用记录等手段,实时掌握计算机运行状态。具体实现包括获取进程信息、解析网络数据包、记录应用使用时长等,确保企业信息安全合规,提升工作效率。本文转载自:[VIPShare](https://www.vipshare.com)。
21 0
|
27天前
|
Go 数据安全/隐私保护 UED
优化Go语言中的网络连接:设置代理超时参数
优化Go语言中的网络连接:设置代理超时参数
|
1月前
|
开发框架 Go 计算机视觉
纯Go语言开发人脸检测、瞳孔/眼睛定位与面部特征检测插件-助力GoFly快速开发框架
开发纯go插件的原因是因为目前 Go 生态系统中几乎所有现有的人脸检测解决方案都是纯粹绑定到一些 C/C++ 库,如 OpenCV 或 dlib,但通过 cgo 调用 C 程序会引入巨大的延迟,并在性能方面产生显著的权衡。此外,在许多情况下,在各种平台上安装 OpenCV 是很麻烦的。使用纯Go开发的插件不仅在开发时方便,在项目部署和项目维护也能省很多时间精力。
|
2月前
|
Go 数据安全/隐私保护 开发者
Go语言开发
【10月更文挑战第26天】Go语言开发
44 3
|
2月前
|
Java 程序员 Go
Go语言的开发
【10月更文挑战第25天】Go语言的开发
40 3