V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
balabalaguguji
V2EX  ›  编程

我来说说异步框架的最大缺点

  •  
  •   balabalaguguji · 2021-04-20 16:04:09 +08:00 · 15639 次点击
    这是一个创建于 1314 天前的主题,其中的信息可能已经有所发展或是发生改变。

    异步大家都在夸,都在说他的好处,但是似乎没人说过他的最大缺点,我来说说吧,避免踩坑。

    异步因为是只有一个线程,如果有一个地方阻塞了,那整个网站全部都卡住了(多进程的另说),所以你得时刻记得,如果会阻塞的方法,就得用异步的库。另外还得确保别写出死循环的逻辑,不然也是卡住整个站。

    异步现在支持最好的应该是 nodejs 吧,各种异步库都有,但是 python 的支持就少很多了,如果用 gevent,猴子补丁不能帮你把所有的接口都补成异步的,所以你得清楚什么方法是可以用的,例如 commands 这个就不能用,没打补丁,可以改用 subprocess 。

    写异步的代码时得时刻提醒自己以上问题,但是如果用多线程模型,就不用担心这些,如果你的网站不是特别大的访问量,可以使用多线程模型,够简单;如果是需要高并发,有大量用户,可以用异步框架并始终记住不要用阻塞方法。

    如果说得不对的地方,请大家指点。

    第 1 条附言  ·  2021-04-20 16:56:14 +08:00
    由于不同人对异步和多线程的理解程度不一样,所以两个方案哪个更简单就见仁见智了,不用把注意力集中在比较上。
    我更多的是想告诉大家写异步程序最应该注意的点是什么(还是别说缺点了,会引起骂战)。
    第 2 条附言  ·  2021-04-20 18:41:22 +08:00
    以上我说的是基于对 nodejs 的了解,不同语言实现不一样,nodejs 是只有一个线程,有些语言据说可以多线程+异步 IO
    第 3 条附言  ·  2021-04-21 09:38:54 +08:00
    异步不同语言的实现方式不一样,nodejs 是单线程基于事件循环 event loop 实现的异步 IO,所以会有我说的问题。
    评论中学习到 golang 的异步实现是很完美的,可以多线程+异步 IO,其他语言据说还有 C#、Java,还有没更多?
    153 条回复    2021-04-22 15:17:11 +08:00
    1  2  
    balabalaguguji
        101
    balabalaguguji  
    OP
       2021-04-21 09:11:14 +08:00
    @xiubin #79 不用异步那就是多线程模型,每个用户进来都是一个独立的线程,卡死也是卡死他自己的,其他人不受影响,而异步是所有人都受影响,这个估计很多新人是不知道的。死循环只是一个卡死的举例
    balabalaguguji
        102
    balabalaguguji  
    OP
       2021-04-21 09:12:36 +08:00
    @zhanlanhuizhang #81 你似乎理解错了异步,我说的是 nodejs 的那种 IO 异步,只有一个主线程,没有多线程
    balabalaguguji
        103
    balabalaguguji  
    OP
       2021-04-21 09:15:28 +08:00
    @xuanbg #83 没有 web 服务器是同步还用单线程的吧,现在流行的就两种,异步和多线程
    balabalaguguji
        104
    balabalaguguji  
    OP
       2021-04-21 09:16:33 +08:00
    @helloworld000 #84 了解过 tornado,你这个没怎么去了解
    Visionhope
        105
    Visionhope  
       2021-04-21 09:17:48 +08:00
    其实异步更简单, 只是大家入门的时候, 最熟悉的就是基于多线程的编程方式, 不熟悉你所说的 python 里面的 asyncio 那种异步编程方式, 归根到底只是个熟悉程度的问题.
    balabalaguguji
        106
    balabalaguguji  
    OP
       2021-04-21 09:18:15 +08:00
    @Muninn #85 我考虑最多的是 go 的包有没有 python 和 nodejs 的多,现在 pip 和 npm 第三方库很多,要啥有啥,很方便
    charlie21
        107
    charlie21  
       2021-04-21 09:18:38 +08:00
    在面向 API 编程解决具体问题时,异步编程有自己一套 API,但是在某个具体事情上使用了这套 API 就要了解它的开销,而不同运行时的开销是不一样的。C# 异步背后是线程池,JS 异步背后是事件循环,golang 背后说什么阿 反正和前两个都不一样

    故而你应该指明你聊的运行时是什么
    balabalaguguji
        108
    balabalaguguji  
    OP
       2021-04-21 09:19:27 +08:00
    @billlee #86 java 没怎么用,能多线程+异步 IO,感觉很好
    balabalaguguji
        109
    balabalaguguji  
    OP
       2021-04-21 09:21:52 +08:00
    @liuhan907 #88 嗯,仅限 nodejs,python 语言,其他语言我不了解
    wuwukai007
        110
    wuwukai007  
       2021-04-21 09:22:05 +08:00
    可以通过 rpc 解耦,不支持异步的库,走 rpc
    balabalaguguji
        111
    balabalaguguji  
    OP
       2021-04-21 09:25:22 +08:00
    @hejingyuan199 #89 我不知道你是不是用 nodejs 或者 python,如果是,那你是理解错了,那我这篇文章提醒到了对的人。异步只有 IO 为异步,只有一个线程,时间切换来达到异步的,并不是创建了多个线程,多个线程的那叫多线程了。
    balabalaguguji
        112
    balabalaguguji  
    OP
       2021-04-21 09:26:40 +08:00
    @ipwx #90 嗯,这个看个人对哪个更加熟悉,没有答案。我尽到提醒大家异步需要主意的点就行。
    balabalaguguji
        113
    balabalaguguji  
    OP
       2021-04-21 09:29:49 +08:00
    @lujjjh #92 对,也就是我说的对代码和人员要求高了,要多 review,多测试,多发现 sync 代码,时刻提醒自己别搞出 block 代码了
    balabalaguguji
        114
    balabalaguguji  
    OP
       2021-04-21 09:31:34 +08:00
    @domodomo #95 到位,是我这个意思。我就是某天不小心写了个同步 IO 调用,卡死了整个网站,所以来提醒大家。
    balabalaguguji
        115
    balabalaguguji  
    OP
       2021-04-21 09:32:33 +08:00
    @wuwukai007 #110 你要等返回结果呢?
    balabalaguguji
        116
    balabalaguguji  
    OP
       2021-04-21 09:33:50 +08:00
    @charlie21 #107 嗯,后面补充了是聊 nodejs 和 python
    Actrace
        117
    Actrace  
       2021-04-21 09:49:14 +08:00
    😂本质上来说所有的多任务都是模拟的,都是需要分时的。
    所以我个人认为编程语言最重要是,面向用户时,提供足够简单易懂的使用方法。具体实现时,实现足够优秀的性能(但是用户不需要知道它怎么做到)。

    所以很多时候,真的是要根据场景来决定编程语言,哪个简单用哪个就完了,甚至对于不同的人都是这样,你觉得顺序编程好理解,那就顺序编程,你觉得异步编程好用,那就异步。没啥好纠结的。
    bleepbloop
        118
    bleepbloop  
       2021-04-21 09:49:15 +08:00
    建议多学习
    LessonOne
        119
    LessonOne  
       2021-04-21 10:25:46 +08:00   ❤️ 1
    @balabalaguguji 不懂就不要误人子弟 每个用户一个线程 那你的服务器不是要爆?
    czzhengkw
        120
    czzhengkw  
       2021-04-21 10:32:47 +08:00
    低情商:我来说说异步框架的最大缺点
    高情商:使用异步框架碰到这些问题,请问要怎么解决
    MaxJin
        121
    MaxJin  
       2021-04-21 10:35:23 +08:00
    。。。js 是单线程,node 是多线程
    SystemLight
        122
    SystemLight  
       2021-04-21 10:42:16 +08:00
    协程只是异步的其中一种实现,多线程同样可以实现异步,而且多线程配合协程实现的异步方式性能非常高,不但解决了 CPU 密集同时还让 IO 密集型尽力节省出 CPU 上下文切换带来的损耗,这一点我感觉.NET5 的 web 框架是做的最好的。
    lllllliu
        123
    lllllliu  
       2021-04-21 10:47:08 +08:00
    A 等 B,B 又是个事件监听,不知道什么时候发生,也不能返回,也不能让 A 抢掉这个监听。
    这玩意在 Node 里写起来贼难受哦。只能强行加 flag+setInterval 1ms.
    lllllliu
        124
    lllllliu  
       2021-04-21 10:51:52 +08:00
    Node 就是没有一个能 hold 住的方法。很难受。不像 go 可以 wait
    guyeu
        125
    guyeu  
       2021-04-21 10:52:26 +08:00   ❤️ 2
    你说的这算什么缺点啊。。。哪怕是纯多线程,你写出了一个死循环或者死锁,就能保证它只阻塞一个线程不影响其他线程?
    还有,异步和多线程并不存在任何冲突,思维不要被单一语言限制,从原理上讲这俩就不是一个维度的东西。

    目前的操作系统只提供了多线程和多进程的调度支持,如果要实现异步,要么是如你所言线程池(单线程也是一种线程池)+回调的方式,要么是 go 这种自行实现了一套协程的调度模型,但这两种最终的落脚点都在操作系统的线程上,因此就目前而言,各种异步编程的实现方式都是对多线程去阻塞的优化,目标还是完美利用多核算力。

    一般而言,公认异步的缺点主要有以下两种:
    1. 对同步代码的破坏性改动,绝大多数异步 API 都具有传染性,类似各种语言的 async 关键字,各种异步库的 Future/Promise/Mono,都不可避免通过函数签名的方式传染调用方( go 这方面就好很多);
    2. 复杂调度和竞争导致的延时,同步可以不用做其他事专心等阻塞逻辑的结果就好,异步就得等线程 /协程调度的结果,让你这个回调执行才能轮到执行,当并发量大的时候长尾效应还是很严重的。
    xiubin
        126
    xiubin  
       2021-04-21 10:53:41 +08:00
    @balabalaguguji #101 那这个问题的本质就不是异步了呀,单线程模型你不用异步,有耗时的任务照样会卡住的啊,有死循环不是照样会卡死吗?
    kksco
        127
    kksco  
       2021-04-21 11:05:40 +08:00
    go 吹来了,go 最大的特点就是所有的代码都是同步的逻辑去写的,非常自然,不用考虑什么 async await 这种关键字,写过 js py go rust,go 这块是做的最好的。异步最大的缺点就是反智。。但是现在也成为了主流,基本都还是要懂要会用。。
    lonelymarried
        128
    lonelymarried  
       2021-04-21 11:08:56 +08:00
    我已经习惯异步了,不是异步的我会纳闷怎么写
    elintwenty
        129
    elintwenty  
       2021-04-21 11:15:26 +08:00
    主题应该加上限定的语言或框架,Java 可以做到多线程异步非阻塞
    异步和多线程没有关系,阻塞和异步也没有关系
    阻塞导致当前线程卡死不是异步的问题
    而且多线程不见得比异步代码好写,只能说明还没遇到多线程的问题而已
    leexy
        130
    leexy  
       2021-04-21 11:30:07 +08:00
    PHP 是最好的编程语言
    hejingyuan199
        131
    hejingyuan199  
       2021-04-21 11:42:07 +08:00
    @balabalaguguji 感谢回复。明白了。
    对于多线程我只用过 java 和 c++。
    nodejs 我虽然在用,但我知道他是单线程同时 IO 非阻塞,那是因为它利用了多条 IO 线程,但这些线程不属于 js 线程而已。
    balabalaguguji
        132
    balabalaguguji  
    OP
       2021-04-21 11:46:44 +08:00
    @LessonOne #119 那请教下正确的是怎样
    balabalaguguji
        133
    balabalaguguji  
    OP
       2021-04-21 11:47:50 +08:00
    @czzhengkw #120 都是程序员,不用那么高情商,直接怼
    balabalaguguji
        134
    balabalaguguji  
    OP
       2021-04-21 11:52:30 +08:00
    @MaxJin #121 又去了解了下,应该说是 nodejs 的代码执行是单线程的,IO 是多线程的
    balabalaguguji
        135
    balabalaguguji  
    OP
       2021-04-21 11:53:36 +08:00
    @SystemLight #122 感谢科普,之前一直以为.NET 很落后
    mmqc
        136
    mmqc  
       2021-04-21 11:54:29 +08:00
    C# 中采用 TAP 的模式去写代码,在可能会出现耗时的地方,直接通过 Task 的调度来手动调度一个额外的线程,来承载具体的业务。而调用 Task 的线程,还是可以继续处理其他问题啊。node 之类的应该也会有类似的机制吧?
    所以我没太看明白,为什么一个卡死,会造成全站崩……
    1more
        137
    1more  
       2021-04-21 12:23:07 +08:00 via iPhone
    Rust 的 async/.await 用着非常舒服,写代码用同步风格就可以
    iyaozhen
        138
    iyaozhen  
       2021-04-21 13:05:50 +08:00
    其实这主要是同步语言、比如 Python 这种后来加了异步的,就很烦。就像 java 一样要考虑这个是不是线程安全啥的。

    node 、go 这种天生异步的好很多
    ipwx
        139
    ipwx  
       2021-04-21 14:00:44 +08:00
    @balabalaguguji 101L

    “@xiubin #79 不用异步那就是多线程模型,每个用户进来都是一个独立的线程,卡死也是卡死他自己的,其他人不受影响,而异步是所有人都受影响,这个估计很多新人是不知道的。死循环只是一个卡死的举例”
    ----

    如果只是这个模型的话确实,多线程好写。但是逻辑复杂一点,经常是各种线程互相协作的时候,你会发现多线程模型、线程池,写起来都经常死锁 bug 。。。 这时候异步、await 、甚至 actor 反而更好写了。
    yazinnnn
        140
    yazinnnn  
       2021-04-21 15:40:05 +08:00
    看了本楼讨论,感觉 vertx 的异步还挺好用的....
    嗯,本来 vertx 也算是 nodejs 的一种 java 实现
    no1xsyzy
        141
    no1xsyzy  
       2021-04-21 15:47:24 +08:00
    @guyeu #125 这两个缺陷其实都是消极非抢占式调度的问题。
    激进非抢占式调度不会导致关键词传染,因为可以通过二次编译,在各种位置打上 await sleep(0)
    但可能导致一些依赖于同步行为的、非线程安全的代码产生异状行为,甚至难以排查的怪异问题。
    目前我所知的激进非抢占调度都是函数式语言
    masterclock
        142
    masterclock  
       2021-04-21 16:01:33 +08:00
    讲异步、IO 方面,应该看看 scala 里 zio,fs2 框架。
    比如 zio:
    ZIO is a library for asynchronous and concurrent programming that is based on pure functional programming.
    结合 scala “默认”不可变对象,lazy 等特性,写代码完全没有焦虑感,连 rust 都不想要了。
    wuwukai007
        143
    wuwukai007  
       2021-04-21 17:29:09 +08:00
    @balabalaguguji rpc 可以走 http 的,把没办法异步的库和逻辑 放在 单独的 rpc 服务里面,主服务通过 aiohttp 调用,不就曲线异步了吗
    ykb8121
        144
    ykb8121  
       2021-04-21 17:59:34 +08:00
    说的怕不是异步的缺点,而是自己代码菜的缺点...
    都用异步了还写阻塞方法,居然还写得出死循环,那这本身就是程序员自己代码的问题,异步这锅不背

    另:py 生态正逐渐完善,大多数应用场景想找其实都能找到。再说手动改异步,除了费时间,远没有你想象的困难,望多学习
    tianyamomo
        145
    tianyamomo  
       2021-04-21 18:38:17 +08:00
    异步代码是不应该出现阻塞代码的
    balabalaguguji
        146
    balabalaguguji  
    OP
       2021-04-21 18:40:44 +08:00
    @tianyamomo #145 这不正是我提醒的
    balabalaguguji
        147
    balabalaguguji  
    OP
       2021-04-21 18:41:35 +08:00
    @ykb8121 #144 学会看文字理解别人意思,少做喷子
    iseki
        148
    iseki  
       2021-04-21 19:28:11 +08:00 via Android
    @balabalaguguji #97 不会吧,don't block me 不是异步大多数有类似问题的非阻塞异步模型重点强调的吗
    guyeu
        149
    guyeu  
       2021-04-21 20:12:38 +08:00
    @no1xsyzy #141 激进非抢占和消极非抢占都是什么意思呀,第一次听到这俩名词,搜了一下也没搜到,能给个链接学习一个吗
    AlexEzio
        150
    AlexEzio  
       2021-04-21 20:26:24 +08:00
    异步编程本身就和多线程编程,多进程编程属于并发编程领域的不同范式。编程时需要注意代码是否是阻塞,就像多线程需要注意各类资源安全问题, 多进程需要考虑资源共享问题。 不同范式,在编程时考虑的关注点是不一样的,这并不是异步的缺点,恰好是其特性;只是异步编程本身调试起来就比较麻烦,很多人没有深入了解过,肯定是会经常阻塞程序的。

    python 你应该了解的并不多。
    cpython 由于 GIL 的存在,多线程模型下,往往只能使用单核的计算资源,因此通过多线程来解决 c10k 问题并不现实,所以在早期才有 twist, gevent 这种第三方的异步库.
    python3.5 原生实现了异步,加入了 async/await 关键字。3.7 使用 c 实现了异步库,社区对各种基本中间件的异步支持也非常好,并不存在你说的支持少的情况。
    go 的优势不需要过多学习,掌握了基础库使用后,就可以写出性能很好的程序. 而不需要关心过多关心底层的调度,并发之类的,这些基础库都帮你做好了。
    no1xsyzy
        151
    no1xsyzy  
       2021-04-22 00:52:34 +08:00
    @guyeu 没有,我只是感性地描述了两种(实际上是光谱)非抢占式调度的差异。
    像 js python 之流,你需要业务代码手动打 await 桩,感知上就是关键词的传染性,这种消极的、将具体的问题下流给业务代码的、Worse is better 哲学的,最终增加了问题的复杂性。
    另一侧来说,打个极端的比方,实质上执行时将每句指令、每个表达式都看作是 await 的,看上去就比需要业务代码手动打 await 桩要激进得多。作为例子,我听说过 Erlang 会在导入代码的时候在各种地方打上调度用的桩,结果就是同步语法,异步语义。天然采用类似 CSP 的 Haskell Monad >>= 也可以实际视同非抢占式调度来执行。
    中间其实还有一些不导致传染的非抢占式调度,比如 call/cc 的方式,本质还是 CSP,问题 1 的语言层面的问题已经充分解决过好几遍了,之所以没有被广泛利用,主要是现有代码的兼容性会有问题。
    至于问题 2,其实是非抢占式的问题,除非做到极端激进,上述 Erlang 那样,简直等于抢占式调度。不然的话大概必须靠优先级队列来调度了,如何设置优先级又是一个问题。
    no1xsyzy
        152
    no1xsyzy  
       2021-04-22 01:06:36 +08:00
    @ykb8121 《 C 陷阱与缺陷》不知道你是否听说过这本书。
    语言设计和具体实现层面上有问题,甚至是不能动的历史遗留问题,导致写起来有心智负担,确实是这些工具有问题。
    但这确实不是异步的问题,而是语言设计和实现的问题。
    照道理,应当从语言层面上保证,根本无法写出阻塞代码,这个简单,把所有外部函数全部赌死;并且可以从实现层面上保证,死循环不应当造成单线程异步卡死,这个其实也简单,只要每当发生循环(从字节码上判断,可以发现有条件或无条件地向低地址跳转),就进行一次让步。

    或者,更激进一点。某个语言的官方设计和官方实现中,超过千万的循环就相当可能导致 kernel kill (OOM),并且官方文档明确地指出这一点是语言的设计目标之一带来的一个副作用。这也是程序员的问题吗?
    MaxJin
        153
    MaxJin  
       2021-04-22 15:17:11 +08:00
    @balabalaguguji 对是这鸭子
    1  2  
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   988 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 19:23 · PVG 03:23 · LAX 11:23 · JFK 14:23
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.