Go语言,你必须掌握的--高效并发模式!

简介: for select 无限循环模式...

or select 无限循环模式

这个模式比较常见,之前文章中的示例也使用过,它一般是和 channel 组合完成任务,格式为:

for { //for 无限循环,或者使用 for range 循环
  select {
    //通过 channel 控制
    case <-done:
      return
    default:
      //执行具体的任务
  }
}
复制代码
  • 这种是 for + select 多路复用的并发模式,哪个 case 满足条件就执行对应的分支,直到有满足退出的条件,才会退出循环。
  • 没有退出条件满足时,则会一直执行 default 分支

for range select 有限循环模式

for _,s:=range []int{}{
   select {
   case <-done:
      return
   case resultCh <- s:
   }
复制代码
  • 一般把迭代的内容发送到 channel 上
  • done channel 用于退出 for 循环
  • resultCh channel 用来接收循环的值,这些值可以通过 resultCh 传递给其他调用者

select timeout 模式

假如一个请求需要访问服务器获取数据,但是可能因为网络问题而迟迟获取不到响应,这时候就需要设置一个超时时间:

package main
import (
  "fmt"
  "time"
)
func main() {
  result := make(chan string)
  timeout := time.After(3 * time.Second) //
  go func() {
    //模拟网络访问
    time.Sleep(5 * time.Second)
    result <- "服务端结果"
  }()
  for {
    select {
    case v := <-result:
      fmt.Println(v)
    case <-timeout:
      fmt.Println("网络访问超时了")
      return
    default:
      fmt.Println("等待...")
      time.Sleep(1 * time.Second)
    }
  }
}
复制代码

运行结果:

等待...
等待...
等待...
网络访问超时了
复制代码
  • select timeout 模式核心是通过 time.After 函数设置的超时时间,防止因为异常造成 select 语句无限等待

注意: 不要写成这样

for {
    select {
    case v := <-result:
      fmt.Println(v)
    case <-time.After(3 * time.Second): //不要写在 select 里面
      fmt.Println("网络访问超时了")
      return
    default:
      fmt.Println("等待...")
      time.Sleep(1 * time.Second)
    }
  }
复制代码

case <- time.After(time.Second) 是本次监听动作的超时时间,意思就说,只有在本次 select 操作中会有效,再次 select 又会重新开始计时,但是有default ,那case 超时操作,肯定执行不到了。

Context 的 WithTimeout 函数超时取消

package main
import (
  "context"
  "fmt"
  "time"
)
func main() {
  // 创建一个子节点的context,3秒后自动超时
  //ctx, stop := context.WithCancel(context.Background())
  ctx, stop := context.WithTimeout(context.Background(), 3*time.Second)
  go func() {
    worker(ctx, "打工人1")
  }()
  go func() {
    worker(ctx, "打工人2")
  }()
  time.Sleep(5*time.Second) //工作5秒后休息
  stop() //5秒后发出停止指令
  fmt.Println("???")
}
func worker(ctx context.Context, name string){
  for {
    select {
    case <- ctx.Done():
      fmt.Println("下班咯~~~")
      return
    default:
      fmt.Println(name, "认真摸鱼中,请勿打扰...")
    }
    time.Sleep(1 * time.Second)
  }
}
复制代码

运行结果:

打工人2 认真摸鱼中,请勿打扰...
打工人1 认真摸鱼中,请勿打扰...
打工人1 认真摸鱼中,请勿打扰...
打工人2 认真摸鱼中,请勿打扰...
打工人2 认真摸鱼中,请勿打扰...
打工人1 认真摸鱼中,请勿打扰...
下班咯~~~
下班咯~~~
//两秒后
???
复制代码
  • 上面示例我们使用了 WithTimeout 函数超时取消,这是比较推荐的一种使用方式

Pipeline 模式

Pipeline 模式也成为流水线模式,模拟现实中的流水线生成。我们以组装手机为例,假设只有三道工序:零件采购、组装、打包成品:

零件采购(工序1)-》组装(工序2)-》打包(工序3)

package main
import (
  "fmt"
)
func main() {
  coms := buy(10)    //采购10套零件
  phones := build(coms) //组装10部手机
  packs := pack(phones) //打包它们以便售卖
  //输出测试,看看效果
  for p := range packs {
    fmt.Println(p)
  }
}
//工序1采购
func buy(n int) <-chan string {
  out := make(chan string)
  go func() {
    defer close(out)
    for i := 1; i <= n; i++ {
      out <- fmt.Sprint("零件", i)
    }
  }()
  return out
}
//工序2组装
func build(in <-chan string) <-chan string {
  out := make(chan string)
  go func() {
    defer close(out)
    for c := range in {
      out <- "组装(" + c + ")"
    }
  }()
  return out
}
//工序3打包
func pack(in <-chan string) <-chan string {
  out := make(chan string)
  go func() {
    defer close(out)
    for c := range in {
      out <- "打包(" + c + ")"
    }
  }()
  return out
}
复制代码

运行结果:

打包(组装(零件1))
打包(组装(零件2))
打包(组装(零件3))
打包(组装(零件4))
打包(组装(零件5))
打包(组装(零件6))
打包(组装(零件7))
打包(组装(零件8))
打包(组装(零件9))
打包(组装(零件10))
复制代码

扇入扇出模式

手机流水线运转后,发现配件组装工序比较耗费时间,导致工序1和工序3也相应的慢了下来,为了提升性能,工序2增加了两班人手:

image.png

  • 根据示意图能看到,红色部分为扇出,蓝色为扇入

改进后的流水线:

package main
import (
  "fmt"
  "sync"
)
func main() {
  coms := buy(10)    //采购10套配件
  //三班人同时组装100部手机
  phones1 := build(coms)
  phones2 := build(coms)
  phones3 := build(coms)
  //汇聚三个channel成一个
  phones := merge(phones1,phones2,phones3)
  packs := pack(phones) //打包它们以便售卖
  //输出测试,看看效果
  for p := range packs {
    fmt.Println(p)
  }
}
//工序1采购
func buy(n int) <-chan string {
  out := make(chan string)
  go func() {
    defer close(out)
    for i := 1; i <= n; i++ {
      out <- fmt.Sprint("零件", i)
    }
  }()
  return out
}
//工序2组装
func build(in <-chan string) <-chan string {
  out := make(chan string)
  go func() {
    defer close(out)
    for c := range in {
      out <- "组装(" + c + ")"
    }
  }()
  return out
}
//工序3打包
func pack(in <-chan string) <-chan string {
  out := make(chan string)
  go func() {
    defer close(out)
    for c := range in {
      out <- "打包(" + c + ")"
    }
  }()
  return out
}
//扇入函数(组件),把多个chanel中的数据发送到一个channel中
func merge(ins ...<-chan string) <-chan string {
  var wg sync.WaitGroup
  out := make(chan string)
  //把一个channel中的数据发送到out中
  p:=func(in <-chan string) {
    defer wg.Done()
    for c := range in {
      out <- c
    }
  }
  wg.Add(len(ins))
  //扇入,需要启动多个goroutine用于处于多个channel中的数据
  for _,cs:=range ins{
    go p(cs)
  }
  //等待所有输入的数据ins处理完,再关闭输出out
  go func() {
    wg.Wait()
    close(out)
  }()
  return out
}
复制代码

运行结果:

打包(组装(零件2))
打包(组装(零件3))
打包(组装(零件1))
打包(组装(零件5))
打包(组装(零件7))
打包(组装(零件4))
打包(组装(零件6))
打包(组装(零件8))
打包(组装(零件9))
打包(组装(零件10))
复制代码
  1. merge 和业务无关,不能当做一道工序,我们应该把它叫做 组件
  2. 组件是可以复用的,类似这种扇入工序,都可以使用 merge 组件

Futures 模式

Pipeline 流水线模式中的工序是相互依赖的,只有上一道工序完成,下一道工序才能开始。但是有的任务之间并不需要相互依赖,所以为了提高性能,这些独立的任务就可以并发执行。

Futures 模式可以理解为未来模式,主协程不用等待子协程返回的结果,可以先去做其他事情,等未来需要子协程结果的时候再来取,如果子协程还没有返回结果,就一直等待。

我们以火锅为例,洗菜、烧水这两个步骤之间没有依赖关系,可以同时做,最后

示例:

package main
import (
  "fmt"
  "time"
)
func main() {
  vegetablesCh := washVegetables() //洗菜
  waterCh := boilWater()           //烧水
  fmt.Println("已经安排好洗菜和烧水了,我先开一局")
  time.Sleep(2 * time.Second)
  fmt.Println("要做火锅了,看看菜和水好了吗")
  vegetables := <-vegetablesCh
  water := <-waterCh
  fmt.Println("准备好了,可以做火锅了:",vegetables,water)
}
//洗菜
func washVegetables() <-chan string {
  vegetables := make(chan string)
  go func() {
    time.Sleep(5 * time.Second)
    vegetables <- "洗好的菜"
  }()
  return vegetables
}
//烧水
func boilWater() <-chan string {
  water := make(chan string)
  go func() {
    time.Sleep(5 * time.Second)
    water <- "烧开的水"
  }()
  return water
}
复制代码

运行结果:

已经安排好洗菜和烧水了,我先开一局
要做火锅了,看看菜和水好了吗
准备好了,可以做火锅了: 洗好的菜 烧开的水
复制代码
  1. Futures 模式下的协程和普通协程最大的区别是可以返回结果,而这个结果会在未来的某个时间点使用。所以在未来获取这个结果的操作必须是一个阻塞的操作,要一直等到获取结果为止。
  2. 如果你的大任务可以拆解为一个个独立并发执行的小任务,并且可以通过这些小任务的结果得出最终大任务的结果,就可以使用 Futures 模式。


相关文章
|
4天前
|
监控 Linux PHP
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
52 20
|
2天前
|
存储 监控 算法
探秘员工泄密行为防线:基于Go语言的布隆过滤器算法解析
在信息爆炸时代,员工泄密行为对企业构成重大威胁。本文聚焦布隆过滤器(Bloom Filter)这一高效数据结构,结合Go语言实现算法,帮助企业识别和预防泄密风险。通过构建正常操作“指纹库”,实时监测员工操作,快速筛查可疑行为。示例代码展示了如何利用布隆过滤器检测异常操作,并提出优化建议,如调整参数、结合日志分析系统等,全方位筑牢企业信息安全防线,守护核心竞争力。
|
10天前
|
Go C语言
Go语言入门:分支结构
本文介绍了Go语言中的条件语句,包括`if...else`、`if...else if`和`switch`结构,并通过多个练习详细解释了它们的用法。`if...else`用于简单的条件判断;`if...else if`处理多条件分支;`switch`则适用于基于不同值的选择逻辑。特别地,文章还介绍了`fallthrough`关键字,用于优化重复代码。通过实例如判断年龄、奇偶数、公交乘车及成绩等级等,帮助读者更好地理解和应用这些结构。
34 14
|
25天前
|
存储 监控 算法
内网监控系统之 Go 语言布隆过滤器算法深度剖析
在数字化时代,内网监控系统对企业和组织的信息安全至关重要。布隆过滤器(Bloom Filter)作为一种高效的数据结构,能够快速判断元素是否存在于集合中,适用于内网监控中的恶意IP和违规域名筛选。本文介绍其原理、优势及Go语言实现,提升系统性能与响应速度,保障信息安全。
28 5
|
1月前
|
算法 安全 Go
Go语言中的加密和解密是如何实现的?
Go语言通过标准库中的`crypto`包提供丰富的加密和解密功能,包括对称加密(如AES)、非对称加密(如RSA、ECDSA)及散列函数(如SHA256)。`encoding/base64`包则用于Base64编码与解码。开发者可根据需求选择合适的算法和密钥,使用这些包进行加密操作。示例代码展示了如何使用`crypto/aes`包实现对称加密。加密和解密操作涉及敏感数据处理,需格外注意安全性。
46 14
|
1月前
|
存储 安全 Go
Go语言中的map数据结构是如何实现的?
Go 语言中的 `map` 是基于哈希表实现的键值对数据结构,支持快速查找、插入和删除操作。其原理涉及哈希函数、桶(Bucket)、动态扩容和哈希冲突处理等关键机制,平均时间复杂度为 O(1)。为了确保线程安全,Go 提供了 `sync.Map` 类型,通过分段锁实现并发访问的安全性。示例代码展示了如何使用自定义结构体和切片模拟 `map` 功能,以及如何使用 `sync.Map` 进行线程安全的操作。
|
10天前
|
监控 关系型数据库 MySQL
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
19 0
|
2月前
|
开发框架 Go 计算机视觉
纯Go语言开发人脸检测、瞳孔/眼睛定位与面部特征检测插件-助力GoFly快速开发框架
开发纯go插件的原因是因为目前 Go 生态系统中几乎所有现有的人脸检测解决方案都是纯粹绑定到一些 C/C++ 库,如 OpenCV 或 dlib,但通过 cgo 调用 C 程序会引入巨大的延迟,并在性能方面产生显著的权衡。此外,在许多情况下,在各种平台上安装 OpenCV 是很麻烦的。使用纯Go开发的插件不仅在开发时方便,在项目部署和项目维护也能省很多时间精力。
|
3月前
|
Go 数据安全/隐私保护 开发者
Go语言开发
【10月更文挑战第26天】Go语言开发
54 3
|
3月前
|
Java 程序员 Go
Go语言的开发
【10月更文挑战第25天】Go语言的开发
47 3