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

25 回复
  1. 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 不会有效果,那个协程还在跑着

#7 fn 无法取消,那么 context 就无意义

那你告诉我使用方定义的 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 是倒退,跟你帖子主题说自己搞的这个东西是否牛逼没关系。

真搞不懂哪里来的优越感和这么喜欢批判别人


这不是优越感,只是我这个人说话比较实在并且直接,你听了可能会不舒服而已。
至于为什么这样不懂得客气,是因为有过太多因为客气委婉、别人反倒以为自己没问题,所以我不想再客气了,有问题就尖酸刻薄地指出,至少对于技术本身,是中肯切实的

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

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

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

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

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

良药苦口,忠言逆耳,认真思考技术就行了,共勉

“真搞不懂哪里来的优越感和这么喜欢批判别人”

补充一点,不是批判 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的小工具时,你可能会发现泛型在处理异步函数返回类型时有些复杂。这是因为泛型在编译时确定类型,而异步函数通常涉及在运行时才能确定的返回类型。

为了解决这个问题,你可以考虑以下策略:

  1. 使用接口类型:定义一个通用的接口类型,让异步函数返回这个接口类型的实例。这样,你可以在编译时保持类型安全,同时允许异步函数返回不同类型的结果。

  2. 使用泛型约束:在Go 1.18及更高版本中,你可以使用泛型约束来限制泛型参数的类型范围。这可以帮助你在编写泛型代码时保持类型安全。

  3. 考虑使用现有的并发库:Go社区已经开发了许多用于处理并发任务的库,这些库可能已经实现了类似async/await的功能。在尝试自己实现之前,先研究一下这些库可能会很有帮助。

希望这些建议能帮到你!如果还有其他问题,欢迎继续提问。

回到顶部