在学习 Go 的泛型,写了个模拟 async/await 的小工具,写的过程中发现个问题

2022-08-30 21:23:40 +08:00
 rrfeng

谁能看出来问题在哪里 #狗头

代码:

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()
2079 次点击
所在节点    Go 编程语言
24 条回复
buffzty
2022-08-31 11:37:16 +08:00
1. panic 就 gg
2. 这里没有限制线程数,如果做成 js 那种 应该是起一个 go 协程做事件循环 然后挨个处理. 可以放在 loop 线程里处理 也可以设置在工作线程中处理
rrfeng
2022-08-31 12:34:24 +08:00
@buffzty 不是这个问题。
lysS
2022-08-31 15:54:03 +08:00
你这样的 cancel 是不会其效果的,比如 fn 是 time.Sleep(time.Secont*30), 那么哪个 goroutin 始终会执行 30s
lysS
2022-08-31 15:57:10 +08:00
要想在自己的逻辑里接入 context, 必须要求业务是可拆分的,执行一段后就去检测是否 cacel
for i := 0; i < 30; i++ {
time.Sleep(time.Second)
select {
case <-ctx.Done():
return
}
}
rrfeng
2022-08-31 15:57:57 +08:00
@lysS cancel 当然有效果,问题不是在这里。
lysS
2022-08-31 15:58:52 +08:00
还要,泛型和 eface 组合毫无意义,泛型和 iface 组合有较大的性能损失 https://www.infoq.cn/article/xprmcl5qbf6yvdroajyn
rrfeng
2022-08-31 16:00:01 +08:00
@lysS 哦你说这个,那是没法取消 fn 的执行,但是 Await() 是会返回的。这个需要调用方传过来的 fn 里自带 context 。
rrfeng
2022-08-31 16:00:53 +08:00
@lysS interface 那个确实没必要。
lysS
2022-08-31 16:03:05 +08:00
@rrfeng #5 cancel 不会有效果,那个协程还在跑着
lysS
2022-08-31 16:03:47 +08:00
@rrfeng #7 fn 无法取消,那么 context 就无意义
rrfeng
2022-08-31 17:43:11 +08:00
@lysS 那你告诉我使用方定义的 fn 你如何取消?
pastor
2022-09-01 14:07:21 +08:00
协程本身就比 async/await 易用、可读性强,OP 搞这种玩意可以加深下自己对协程之类的玩法,但如果真应用到业务里,那就是坑队友了。

我昨天看了这个帖子标题手滑点进来都没看内容就直接就给关闭了,今天发现竟然有人回复,就又点进来

本末倒置的玩法,不值得浪费时间,奉劝各位早点散了吧
rrfeng
2022-09-01 17:19:11 +08:00
@pastor
现在你有个方法里要调用 10 个没有先后顺序的外部接口,每个要花费 1s ,你会怎么写?
pastor
2022-09-01 18:26:46 +08:00
@rrfeng
第一,如果没有先后顺序,那有序调用也是满足要求的,for 循环挨个调用就可以
第二,如果有性能要求,同时去请求 10 个才能满足性能,那 wg.Add(10) go func() { defer wg.Done() ... } 也比 async/await 可读性舒服得多,如果这种异步量大这里可以用协程池而不是直接 go

对于异步理解比较到位的人二院,async/await 并不比 Promise 之类算是改进,相比于 go 可读性就更不直观了
pastor
2022-09-01 18:31:17 +08:00
还有就是,如果你的业务依赖这种同时多个异步的,最麻烦的地方并不是封装这种 async/await 的绕脑的写法,而是实际场景中每个异步请求可能失败后怎么处理。

这对于不同的业务场景没有固定答案,比如爬虫或者什么,失败了也影响不大;但是对于具有事务性要求的业务,这种同时依赖多个异步远不如串行顺序处理好。对性能有很高要求的八成也应该是依赖自家的基础设施,这种如果还能设计成同时多个异步,那说明你们整体架构已经出问题了、比如微服务拆分得非常不合理,这种要治病得从架构顶层往下梳理而不是脚疼医脚。
pastor
2022-09-01 18:35:44 +08:00
go 的哲学,就是让大家从语法语义中解放出来,这种 async/await 的设计,其实本质上都不算是 lib 封装了,而是更偏于语法语义的语法糖的设计。不管花多少时间玩这种东西,到头来总有一天会想明白,发现竹篮打水。越早回头是岸越划算
pastor
2022-09-01 18:36:37 +08:00
@pastor #14 "二院" -> "而言"
rrfeng
2022-09-02 11:20:35 +08:00
@pastor 要不是楼主是我,我还以为楼主在那高喊『我用 go 协程实现了超牛逼的 async/await 语法』呢。就是个语法糖,没必要扯什么 go 哲学和架构设计吧。
pastor
2022-09-02 16:57:14 +08:00
@rrfeng 我只是劝你别研究这种吃力不讨好的东西了,如果你目前阶段的修为 get 不到,就忽略我说的吧。期待未来的某天或许你会恍然大悟
pastor
2022-09-02 16:59:11 +08:00
这玩意相比与 goroutine 是倒退,跟你帖子主题说自己搞的这个东西是否牛逼没关系。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/876569

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX