Golang Go语言中学习泛型时,写了个模拟async/await的小工具,写的过程中发现个问题
谁能看出来问题在哪里 #狗头
代码:
package future
import (
“context”
)
type Future[T any] interface {
Await() (T, error)
Cancel()
}
type future[T any] struct {
ret chan result[T]
ctx context.Context
cancel context.CancelFunc
}
type result[T any] struct {
dat T
err error
}
func Async[T any](fn func() (T, error)) Future[T] {
return AsyncWithContext(context.Background(), fn)
}
func AsyncWithContext[T any](ctx context.Context, fn func() (T, error)) Future[T] {
c := make(chan result[T], 1)
fctx, cancel := context.WithCancel(ctx)
go func() {
ret, err := fn()
c <- result[T]{dat: ret, err: err}
}()
return &future[T]{ret: c, ctx: fctx, cancel: cancel}
}
func (f *future[T]) Await() (T, error) {
var result result[T]
select {
case <-f.ctx.Done():
result.err = f.ctx.Err()
case ret := <-f.ret:
result = ret
}
f.cancel()
return result.dat, result.err
}
func (f *future[T]) Cancel() {
f.cancel()
}
用法是:
f := future.Async(myfunc)
// 去干其他事
result, err := f.Await()
Golang Go语言中学习泛型时,写了个模拟async/await的小工具,写的过程中发现个问题
更多关于Golang Go语言中学习泛型时,写了个模拟async/await的小工具,写的过程中发现个问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html
- panic 就 gg
2. 这里没有限制线程数,如果做成 js 那种 应该是起一个 go 协程做事件循环 然后挨个处理. 可以放在 loop 线程里处理 也可以设置在工作线程中处理
更多关于Golang Go语言中学习泛型时,写了个模拟async/await的小工具,写的过程中发现个问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
不是这个问题。
你这样的 cancel 是不会其效果的,比如 fn 是 time.Sleep(time.Secont*30), 那么哪个 goroutin 始终会执行 30s
要想在自己的逻辑里接入 context, 必须要求业务是可拆分的,执行一段后就去检测是否 cacel
for i := 0; i < 30; i++ {
time.Sleep(time.Second)
select {
case <-ctx.Done():
return
}
}
cancel 当然有效果,问题不是在这里。
还要,泛型和 eface 组合毫无意义,泛型和 iface 组合有较大的性能损失 https://www.infoq.cn/article/xprmcl5qbf6yvdroajyn
哦你说这个,那是没法取消 fn 的执行,但是 Await() 是会返回的。这个需要调用方传过来的 fn 里自带 context 。
interface 那个确实没必要。
#5 cancel 不会有效果,那个协程还在跑着
那你告诉我使用方定义的 fn 你如何取消?
协程本身就比 async/await 易用、可读性强,OP 搞这种玩意可以加深下自己对协程之类的玩法,但如果真应用到业务里,那就是坑队友了。
我昨天看了这个帖子标题手滑点进来都没看内容就直接就给关闭了,今天发现竟然有人回复,就又点进来
本末倒置的玩法,不值得浪费时间,奉劝各位早点散了吧
现在你有个方法里要调用 10 个没有先后顺序的外部接口,每个要花费 1s ,你会怎么写?
第一,如果没有先后顺序,那有序调用也是满足要求的,for 循环挨个调用就可以
第二,如果有性能要求,同时去请求 10 个才能满足性能,那 wg.Add(10) go func() { defer wg.Done() … } 也比 async/await 可读性舒服得多,如果这种异步量大这里可以用协程池而不是直接 go
对于异步理解比较到位的人二院,async/await 并不比 Promise 之类算是改进,相比于 go 可读性就更不直观了
还有就是,如果你的业务依赖这种同时多个异步的,最麻烦的地方并不是封装这种 async/await 的绕脑的写法,而是实际场景中每个异步请求可能失败后怎么处理。
这对于不同的业务场景没有固定答案,比如爬虫或者什么,失败了也影响不大;但是对于具有事务性要求的业务,这种同时依赖多个异步远不如串行顺序处理好。对性能有很高要求的八成也应该是依赖自家的基础设施,这种如果还能设计成同时多个异步,那说明你们整体架构已经出问题了、比如微服务拆分得非常不合理,这种要治病得从架构顶层往下梳理而不是脚疼医脚。
go 的哲学,就是让大家从语法语义中解放出来,这种 async/await 的设计,其实本质上都不算是 lib 封装了,而是更偏于语法语义的语法糖的设计。不管花多少时间玩这种东西,到头来总有一天会想明白,发现竹篮打水。越早回头是岸越划算
#14 “二院” -> “而言”
要不是楼主是我,我还以为楼主在那高喊『我用 go 协程实现了超牛逼的 async/await 语法』呢。就是个语法糖,没必要扯什么 go 哲学和架构设计吧。
我只是劝你别研究这种吃力不讨好的东西了,如果你目前阶段的修为 get 不到,就忽略我说的吧。期待未来的某天或许你会恍然大悟
这玩意相比与 goroutine 是倒退,跟你帖子主题说自己搞的这个东西是否牛逼没关系。
真搞不懂哪里来的优越感和这么喜欢批判别人
“真搞不懂哪里来的优越感和这么喜欢批判别人”
补充一点,不是批判 OP 你这个人,是说这个语法糖这个实现
你需要 [tomb]( https://pkg.go.dev/gopkg.in/tomb.v2)
可以看這篇介紹: http://blog.labix.org/2011/10/09/death-of-goroutines-under-control
你似乎注意到了 Go Routine 的一大痛點,也就是沒有返回值和錯誤信息。
Tomb 提供了一個描述異步過程生命週期的類型,可以很好地管理異步生命週期。
但是 Tomb 仍然只是最基礎的生命週期描述符,它本身不具備多個異步生命週期的函數式組合。
因此我寫了 [go-rx]( https://github.com/go-rx/rx) 你可以看看,是基於 Tomb 的,結合了 Rx ( Reactive Extension )異步函數式語言的異步生命週期管理和組合用的庫。你可以併發多個 Go Routine 然後合併它們的返回值和錯誤信息,可以使用函數式的語言進行 piping 等等。
可以看我這篇文章瞭解更多: https://dev.to/rix/rx-with-go-generics-2fl6
当然,我很乐意帮你解答关于Go语言中泛型及模拟async/await工具的问题。以下是我的回复:
在Go语言中,泛型是一个强大的工具,允许你编写更加通用和可复用的代码。然而,在尝试模拟async/await模式时,你可能会遇到一些挑战,因为Go的并发模型主要是基于goroutines和channels的。
首先,要明确的是,Go并没有内置的async/await语法,这是JavaScript等语言中的特性。在Go中,我们通常使用goroutines和channels来处理并发任务。
在编写模拟async/await的小工具时,你可能会发现泛型在处理异步函数返回类型时有些复杂。这是因为泛型在编译时确定类型,而异步函数通常涉及在运行时才能确定的返回类型。
为了解决这个问题,你可以考虑以下策略:
-
使用接口类型:定义一个通用的接口类型,让异步函数返回这个接口类型的实例。这样,你可以在编译时保持类型安全,同时允许异步函数返回不同类型的结果。
-
使用泛型约束:在Go 1.18及更高版本中,你可以使用泛型约束来限制泛型参数的类型范围。这可以帮助你在编写泛型代码时保持类型安全。
-
考虑使用现有的并发库:Go社区已经开发了许多用于处理并发任务的库,这些库可能已经实现了类似async/await的功能。在尝试自己实现之前,先研究一下这些库可能会很有帮助。
希望这些建议能帮到你!如果还有其他问题,欢迎继续提问。