在学习 Go 的泛型,写了个模拟 async/await 的小工具,写的过程中发现个问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
rrfeng
V2EX    Go 编程语言

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

  •  
  •   rrfeng 2022-08-30 21:23:40 +08:00 2495 次点击
    这是一个创建于 1150 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    代码:

    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() 
    第 1 条附言    2022-08-31 17:46:52 +08:00
    这里的问题是:
    如果你等待了足够长的时间(大于用户方法的执行时间和 context 的结束时间)后再调用 Await() 的话,得到的结果是随机的。

    因为 select 两个已经就绪的 channel 是随机选择的。
    24 条回复    2022-09-09 16:22:36 +08:00
    buffzty
        1
    buffzty  
       2022-08-31 11:37:16 +08:00
    1. panic 就 gg
    2. 这里没有限制线程数,如果做成 js 那种 应该是起一个 go 协程做事件循环 然后挨个处理. 可以放在 loop 线程里处理 也可以设置在工作线程中处理
    rrfeng
        2
    rrfeng  
    OP
       2022-08-31 12:34:24 +08:00
    @buffzty 不是这个问题。
    lysS
        3
    lysS  
       2022-08-31 15:54:03 +08:00
    你这样的 cancel 是不会其效果的,比如 fn 是 time.Sleep(time.Secont*30), 那么哪个 goroutin 始终会执行 30s
    lysS
        4
    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
        5
    rrfeng  
    OP
       2022-08-31 15:57:57 +08:00
    @lysS cancel 当然有效果,问题不是在这里。
    lysS
        6
    lysS  
       2022-08-31 15:58:52 +08:00
    还要,泛型和 eface 组合毫无意义,泛型和 iface 组合有较大的性能损失 https://www.infoq.cn/article/xprmcl5qbf6yvdroajyn
    rrfeng
        7
    rrfeng  
    OP
       2022-08-31 16:00:01 +08:00
    @lysS 哦你说这个,那是没法取消 fn 的执行,但是 Await() 是会返回的。这个需要调用方传过来的 fn 里自带 context 。
    rrfeng
        8
    rrfeng  
    OP
       2022-08-31 16:00:53 +08:00
    @lysS interface 那个确实没必要。
    lysS
        9
    lysS  
       2022-08-31 16:03:05 +08:00
    @rrfeng #5 cancel 不会有效果,那个协程还在跑着
    lysS
        10
    lysS  
       2022-08-31 16:03:47 +08:00
    @rrfeng #7 fn 无法取消,那么 context 就无意义
    rrfeng
        11
    rrfeng  
    OP
       2022-08-31 17:43:11 +08:00
    @lysS 那你告诉我使用方定义的 fn 你如何取消?
    pastor
        12
    pastor  
       2022-09-01 14:07:21 +08:00
    协程本身就比 async/await 易用、可读性强,OP 搞这种玩意可以加深下自己对协程之类的玩法,但如果真应用到业务里,那就是坑队友了。

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

    本末倒置的玩法,不值得浪费时间,奉劝各位早点散了吧
    rrfeng
        13
    rrfeng  
    OP
       2022-09-01 17:19:11 +08:00
    @pastor
    现在你有个方法里要调用 10 个没有先后顺序的外部接口,每个要花费 1s ,你会怎么写?
    pastor
        14
    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
        15
    pastor  
       2022-09-01 18:31:17 +08:00
    还有就是,如果你的业务依赖这种同时多个异步的,最麻烦的地方并不是封装这种 async/await 的绕脑的写法,而是实际场景中每个异步请求可能失败后怎么处理。

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

    批判跟优越感也没有直接关系。
    批判纯粹是因为你做的这个语法糖是一种倒退,如果没有其他人参与讨论我就不会来留言了,但是看到其他人也参与了讨论并且没有意识到这种语法糖是倒退,这就可能导致有更多人被误导。

    同样有一些其他人参考其他语言做一些对于 go 而言是倒退的东西,如果力所能及,我也都会献上一些建议不要这样做的刻薄说辞

    但我不只是空口乱喷,讲了一些点的,OP 要是能静下心来回到技术本身,对自己是有好处的

    OP 不要纠结我的说话方式,你就当我是个没礼貌的小学生无视我的不客气好了。对其他人也一样,每个人隔三差五总会遇到让自己不舒服的人,我们没法改变环境,但是能适应环境,只要不是切实伤害,自己内心强大就无所谓别人客气不客气了

    已经好些人说我刻薄之类的了,我自己也知道并且欣赏自己的刻薄。刻薄并不是什么坏事情,这世界,总是需要有一些刻薄的人的

    良药苦口,忠言逆耳,认真思考技术就行了,共勉
    pastor
        23
    pastor  
       2022-09-02 19:20:51 +08:00
    @rrfeng “真搞不懂哪里来的优越感和这么喜欢批判别人”

    补充一点,不是批判 OP 你这个人,是说这个语法糖这个实现
    rix
        24
    rix  
       2022-09-09 16:22:36 +08:00
    你需要 [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
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2181 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 30ms UTC 16:02 PVG 00:02 LAX 09:02 JFK 12:02
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86