一个颇为巧妙的go并发模式:or pattern

2024-04-30 21:48

本文主要是介绍一个颇为巧妙的go并发模式:or pattern,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

golang由于对多并发的支持,它被广泛采用与后台开发。它特色的routine是一种高效的轻量级线程,采用go语言,再加上它所提供的各种支持高并发的语言机制,使得用它开发可扩展性高的后台服务器变得容易很多。

我使用go一段时间,在最近学习过程中发现一个颇为巧妙的并发模式,因此想总结一下。go的最大特色是goroutine和channel,不同routine之间能通过channel进行数据发送和通讯,假设我们发动了很多个routine用于请求某项服务,只要其中一个完成了,其他routine就可以直接结束,面对这样的需求情况该如何有效的处理呢。

如果routine的数量不多的情况下,例如我们发动了3个routine向远程服务器发出请求,只要有一个回应了,所有routine就可以结束,假设每个routine都返回一个channel,一旦成功请求到远程服务,它就把返回结果写入到它对应的channel中,示例如下:

remoteServiceRequest := func(ip string, port int) <-chan string {result := make(chan string)//请求远程服务,并把请求结果写入到result...return result
}//启动3个并发请求
var serviceRes = make([] <-chan string, 0, 3)
for i := 0; i < 3; i++ {serviceRes[i] = remoteServiceRequest(remote_ip, remote_port)
}//等到3个请求,只要其中有一个返回即可
select {
case res0 := <-serviceRes[0]:do_something(res0)case res1 := <-serviceRes[1]:do_something(res1)case res2 := <-serviceRes[2]:do_something(res2)      
}

在go中,要等待若干个异步请求返回时,就可以使用select模式,问题在于如果请求的数量较少上面模式可行,当请求的数量非常多,例如有十几二十个,或是有上百个时,我们不可能使用几十个case分支,更麻烦在于很多时候我们并不能提前得知确切的异步请求数量,那么当我们希望在实现的目的为:在任意个异步请求发起的情况下,只要其中有一个完成,那么就获取结果,要灵活的实现这个目标该怎么办?这里就可以使用一个设计颇为巧妙的or 模式。

它的基本思路为,假设我们现在要发起一百个异步请求,显然我们不能在select下面分别罗列100个case,万一到时候需要的是150个怎办?但我们可以通过递归的方式实现case的自动化罗列,于是我可以在函数中先考虑只有3个case的情况,假设现在来了6个异步请求,那么我就可以通过函数先处理前3个请求,然后再递归的去处理接下来的3个请求,也就是通过递归的方式来实现任意个case的罗列,我们看看具体例子:

package main import ("fmt""time"
)func main() {var or func(channels ...<-chan interface{}) <-chan interface{}/*or 函数将多个channel合并在一起,只要其中有一个channel有数据那么就可以返回*/or = func(channels ...<-chan interface{}) <-chan interface{} {//如果只有0个或1个channel,那么直接返回switch len(channels) {case 0:return nil case 1:return channels[0]}/*如果有3个以上的channel,那么下面先针对前3个channel进行select case,然后递归的对余下的channel进行select case*/orDone := make(chan interface{})go func() {defer close(orDone)switch len(channels) {case 2:select {case <-channels[0]:case <-channels[1]:}default:select {case <-channels[0]:case <-channels[1]:case <-channels[2]://递归对3个之后的channel进行select casecase <-or(append(channels[3:], orDone)...):}}}()return orDone }sig := func(after time.Duration, done <-chan interface{}) <-chan interface{} {c := make(chan interface{})go func() {defer close(c)select {case <-done:case <-time.After(after):}}()return c }start := time.Now()done := make(chan interface{})<-or(sig(2*time.Hour, done), sig(5*time.Minute, done), sig(1*time.Second, done), sig(3 * time.Hour, done), sig(1 * time.Minute, done))close(done)fmt.Printf("done after %v", time.Since(start))
}

我们看看上面代码的基本逻辑,假设我们有6个异步请求返回的channel需要等待,那么把6个channel放入or函数后,前3个会在default下面的select被监控,然后代码创建一个orDone channel,然后将它和余下的3个channel再递归的传入or函数。假设第4个channel最先获得数据,那么递归调用的or函数就会在default部分的select case的阻塞中返回,于是它创建的orDone就会因为”defer close(orDone)"的执行而关闭。

于是在第一次递归时对应的语句"<-or(append(channel[3:], orDone)"就会返回,于是就会触发第一次调用or函数是所创建的orDone被关闭,于是最外层调用or函数时就能返回,如果不是后3个channel有数据,而是前3个channel有数据返回,那么递归前的orDone就会被关闭,这就导致递归调用or函数结束阻塞,于是算法就不用再等待后3个channel有数据返回,由此看来这个模式的设计还是非常巧妙的。

在示例代码中,我们启动了多个时钟,然后sig函数会等待时钟结束,然后把这些时钟对应的channel都放入or函数,里面最短的时钟是1秒,因此or函数会在等待1秒后直接返回,由此可见or模式在高并发场景下还是非常适用的。

这篇关于一个颇为巧妙的go并发模式:or pattern的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/950008

相关文章

Go信号处理如何优雅地关闭你的应用

《Go信号处理如何优雅地关闭你的应用》Go中的优雅关闭机制使得在应用程序接收到终止信号时,能够进行平滑的资源清理,通过使用context来管理goroutine的生命周期,结合signal... 目录1. 什么是信号处理?2. 如何优雅地关闭 Go 应用?3. 代码实现3.1 基本的信号捕获和优雅关闭3.2

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

高并发环境中保持幂等性

在高并发环境中保持幂等性是一项重要的挑战。幂等性指的是无论操作执行多少次,其效果都是相同的。确保操作的幂等性可以避免重复执行带来的副作用。以下是一些保持幂等性的常用方法: 唯一标识符: 请求唯一标识:在每次请求中引入唯一标识符(如 UUID 或者生成的唯一 ID),在处理请求时,系统可以检查这个标识符是否已经处理过,如果是,则忽略重复请求。幂等键(Idempotency Key):客户端在每次

Go Playground 在线编程环境

For all examples in this and the next chapter, we will use Go Playground. Go Playground represents a web service that can run programs written in Go. It can be opened in a web browser using the follow

go基础知识归纳总结

无缓冲的 channel 和有缓冲的 channel 的区别? 在 Go 语言中,channel 是用来在 goroutines 之间传递数据的主要机制。它们有两种类型:无缓冲的 channel 和有缓冲的 channel。 无缓冲的 channel 行为:无缓冲的 channel 是一种同步的通信方式,发送和接收必须同时发生。如果一个 goroutine 试图通过无缓冲 channel

如何确定 Go 语言中 HTTP 连接池的最佳参数?

确定 Go 语言中 HTTP 连接池的最佳参数可以通过以下几种方式: 一、分析应用场景和需求 并发请求量: 确定应用程序在特定时间段内可能同时发起的 HTTP 请求数量。如果并发请求量很高,需要设置较大的连接池参数以满足需求。例如,对于一个高并发的 Web 服务,可能同时有数百个请求在处理,此时需要较大的连接池大小。可以通过压力测试工具模拟高并发场景,观察系统在不同并发请求下的性能表现,从而

模版方法模式template method

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/template-method 超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 上层接口有默认实现的方法和子类需要自己实现的方法

【iOS】MVC模式

MVC模式 MVC模式MVC模式demo MVC模式 MVC模式全称为model(模型)view(视图)controller(控制器),他分为三个不同的层分别负责不同的职责。 View:该层用于存放视图,该层中我们可以对页面及控件进行布局。Model:模型一般都拥有很好的可复用性,在该层中,我们可以统一管理一些数据。Controlller:该层充当一个CPU的功能,即该应用程序

迭代器模式iterator

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/iterator 不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素

《x86汇编语言:从实模式到保护模式》视频来了

《x86汇编语言:从实模式到保护模式》视频来了 很多朋友留言,说我的专栏《x86汇编语言:从实模式到保护模式》写得很详细,还有的朋友希望我能写得更细,最好是覆盖全书的所有章节。 毕竟我不是作者,只有作者的解读才是最权威的。 当初我学习这本书的时候,只能靠自己摸索,网上搜不到什么好资源。 如果你正在学这本书或者汇编语言,那你有福气了。 本书作者李忠老师,以此书为蓝本,录制了全套视频。 试