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

从业时间越久,发现互联网很多知识都是错的, 对小白误导有多深?

  •  
  •   jeesk · 2022-04-18 22:04:26 +08:00 · 16429 次点击
    这是一个创建于 739 天前的主题,其中的信息可能已经有所发展或是发生改变。
    说说我自己的看法, 无论是 csdn 还是知乎, 在我最开始从业 java 的时候,觉得他们说得没有毛病? 从业几年后,发现很多都是在鬼扯。 就拿 BIO 和 NIO 谁性能好, 知乎上面竞争激烈,下面我粘贴一个知乎的回答。

    回答 1:

    首先要明确一点:nio 的适用于 io 高并发场景线程开销包括:内存开销(创建一个线程需要为其分配和维护一个 tcb 线程控制块保存上下文,一个线程私有的栈)、线程上下文切换的时间开销(保存上下文,加载下一个线程的上下文),在高并发环境下多出来的这一点点开销积累起来是非常可观的。若使用 bio ,高并发必然引起激烈 contention ,导致大量的上下文切换,若采用一个 io 一个线程模型,一个线程 M 级的空间消耗,内存消耗不起。而 netty 采用 nio 加 selector 加线程池基本上解决了上述问题:一是线程池限制了内存消耗,二是采用 selector 使得只有处于活动状态的 io 才会去占用线程,不会使线程白白因为 io 而阻塞,提高了线程利用率。

    说说他们的谬论:
    1. 使用 BIO 上下文切换厉害, 如果是相同 4 核 cpu , 无论我是用 bio 还是 nio ,都用 200 个线程, 这个时候对 cpu 的竞争到底有多剧烈? 我个人觉得差不了多少。 所以这个说法是错的。

    2. 若采用一个 io 一个线程模型,一个线程 M 级的空间消耗。 这个就更扯淡了。 即使是 tomcat 在 8.5 以前也是 BIO 200 个线程, 都没有用到 1w 个线程? 为什么非要扯开大量线程呢? 并且 tomcat 在 8.5 以后才默认 nio.

    3. 一是线程池限制了内存消耗,二是采用 selector 使得只有处于活动状态的 io 才会去占用线程. 那我 tomcat 用 BIO 没有内存限制? 没有内存限制岂不是早就宕机了? 再说说 selector 的问题, 我 NIO 在 readSelector 开 10 个线程去调用 select, 不都是阻塞的吗? 怎么会说在活动状态才占用线程?

    然后你会发现这些错误的回答有很多,下面还有大量的小白点赞,觉得说得很对。 但是一经脑子思考就发现, 这绝对是坑 B.

    如果有不同意见的小伙伴可以留言,我觉得这个可以作为一个面试题。
    第 1 条附言  ·  2022-04-19 21:46:42 +08:00
    关于我的帖子的一些说法错误

    1.nio 和 bio 的内存占用问题,理论上能用少量线程占用内存少,其实我测试过即使开到 200 甚至 2000 ,内存占用变化不大。 可能是我代码测试问题?
    2. 在使用 tomcat 的 nio 的时候使用 200 线程我是假设。 我们生产应用的线程有些直接用的 cpu 核心数,或者核心数*2.
    3. 关于阻塞问题我应该是理解错了。 nio 为什么能够使用 20 线程代替 bio 200 线程, 因为 nio 不会阻塞线程, 事件来了就处理。 而 bio 是会阻塞。

    关于回复里面有些错误说法:

    1. nio 性能压榨 bio , 其实是错的。我自己用 4 核 8g 的服务器测试过。webflux 并不能缩短应用处理时间,只有让时间更加平稳,而普通 bio 的处理时间波动相当大。
    2. bio 等待阻塞会占用 cpu 。 不会占用 cpu , 当然你要说自旋锁之类的占用 cpu , 那是因为在做 while 循环, 那不算。
    152 条回复    2022-04-21 11:56:01 +08:00
    1  2  
    Narcissu5
        101
    Narcissu5  
       2022-04-19 15:14:34 +08:00
    异步不纯粹就纯粹不异步,java 这种提供一部分接口然后就没有了的方案缺失很鸡肋。尤其是 jdbc 没有异步接口是个绕不过去的问题。当然你可以开线程池,最后就发现大量线程还是 block 在数据库上
    jeesk
        102
    jeesk  
    OP
       2022-04-19 15:16:33 +08:00 via Android
    @lmshl 大部分应用都是 io , 只有交易撮合, 广告竞价,算法之类的应用是 cpu 应用。
    byte10
        103
    byte10  
       2022-04-19 18:41:34 +08:00
    @Jekins 可以看看那个 B 战-小巫老师,视频:NIO 场景和实战,有很多场景和代码验证。还有一个 NIO 服务的绝对优势,看完你就知道小白的问题在哪里了。里面还有一个多线程的讲解,也有代码验证 《最大线程数计算公式》的例子。大多数人都不知道怎么去理解 NIO ,他们以为 用上 tomcat 8.x 后的就以为用上了 NIO 了,实际上还差一些。

    @xxfye 正确,回答正确。tomcat NIO 要开 200 个线程,那是因为业务层“一般”一般一般依然使用阻塞模型,如果使用异步模型,那么只要少量的线程即可,但是需要使用者去主动设置。

    关于网上说的:bio 上下文竞争比 nio 强烈 。因为 NIO 一般只需要设置等于 cpu 核心数即可,BIO 需要设置大量的线程,所以结果是:bio 上下文竞争比 nio 强烈。因果关系没搞明白吗?

    如果你想测试多线程开销到底有多大的影响,我代码中有给出例子,你可以使用 TestMaxThreadNum.calculationCpuTime() 去验证,第一次设置 cpu 核心数量,第二次设置 2000 个线程,你就会发现 2000 大量的线程 跑起来没有线程数量少的快( cpu 密集型情况下)。所以 ”线程数量多“ 的上下文竞争 会比 ”线程数量少“ 的厉害,性能会有所下降。

    另外 BIO 上下文竞争是否会比 NIO 厉害,这里要区分情况,如果 BIO 线程中经常出现 IO 等待,那么不会有特别多的线程竞争。只有在 IO 时间短,且存在大量的线程的情况下,才会出现这样的情况。要减少这种情况,就需要在业务 IO 时间短的时候,使用少量线程,这不好设置和判断。而 NIO 则可以无视 IO 时间,我在视频中有讲过。

    能救一个算一个。
    sampeng
        104
    sampeng  
       2022-04-19 18:52:13 +08:00
    我觉得有个逻辑很多人是不知道的。。都是盲目开线程数。也不管是不是 nio 还是 bio 模型。
    另外,其实在正常情况,只要你的接口足够快,哪怕 2 个线程都够用。
    lmshl
        105
    lmshl  
       2022-04-19 19:40:04 +08:00   ❤️ 1
    大多数人都是先把线程池用💩了,再学美团动态线程池💩上雕花,并不知道一开始就把事情做对,反而去质疑教科书是不是写错了。
    statumer
        106
    statumer  
       2022-04-19 20:02:29 +08:00
    再说个有意思的事,OP 提到的这个回答我找了下原地址

    为什么 nio 效率会比 bio 高? - 锭阳的回答 - 知乎
    https://www.zhihu.com/question/59356897/answer/165104063

    也就 15 个人点赞,排名也很靠后。并没有很多小白点赞嘛。

    [Imgur]( https://imgur.com/sGShzm3)
    rahuahua
        107
    rahuahua  
       2022-04-19 20:32:07 +08:00
    1. 使用 BIO 上下文切换厉害, 如果是相同 4 核 cpu , 无论我是用 bio 还是 nio ,都用 200 个线程, 这个时候对 cpu 的竞争到底有多剧烈? 我个人觉得差不了多少。 所以这个说法是错的。

    都用 200 个线程......
    jeesk
        108
    jeesk  
    OP
       2022-04-19 21:15:54 +08:00 via Android
    @statumer 这个回答是第一名。 不知道为什么。
    jeesk
        109
    jeesk  
    OP
       2022-04-19 21:26:11 +08:00 via Android
    @lmshl 压榨? 有测试代码吗? 还是你随口说的? 我测试过 webflux 和 servlet 差距并不大。 可以看看我回复里面的压测文章, 发在简书里面的。
    jeesk
        110
    jeesk  
    OP
       2022-04-19 21:26:24 +08:00 via Android
    @sampeng 你说的是对的。
    jeesk
        111
    jeesk  
    OP
       2022-04-19 21:27:30 +08:00 via Android
    @rahuahua 我是说假如。 并不是真的。 我们生产有些应用线程直接使用的核数。 因为是 cpu 密集应用。
    jeesk
        112
    jeesk  
    OP
       2022-04-19 21:40:10 +08:00 via Android
    @sampeng 你说的不就是 redis 吗 😆
    maximum2012
        113
    maximum2012  
       2022-04-19 21:51:31 +08:00
    @seagull7558 他不是翻译的核心力量,他基本上就是打杂的
    jeesk
        114
    jeesk  
    OP
       2022-04-19 21:54:58 +08:00 via Android
    tomcat 的 nio 并不是全部是非阻塞的,分为 4 部分, 读请求头,读 request body ,写响应头和 body , 等待下一个连接,只有读 request body 和写请求头 body 体都是阻塞的。
    lmshl
        115
    lmshl  
       2022-04-19 21:57:56 +08:00   ❤️ 1
    @jeesk 有做过社区分享,数据是 Prometheus 实时采集的 3 Pod CPU 合计,TPS 也是来自 Ingress 和 服务内暴露的。
    我的应用是全异步,包括 HTTP 和 RPC 调用,数据库访问走 hikaricp JDBC 链接池,但技术栈是 Scala 这边的,Akka Stream 混合 ZIO 纤程调度,线程池仅有 N 个 fiber scheduler + 2N 个 Blocking 。
    lmshl
        116
    lmshl  
       2022-04-19 22:01:59 +08:00
    Scala 生态里的 HTTP Body 读写都是纯异步的😏
    chihiro2014
        117
    chihiro2014  
       2022-04-19 22:57:39 +08:00
    @jeesk webflux 只是更抗压而已,个人感觉它的背压机制,只是一种限流优化而已。国外 Josh Long 也说过,其实 Webflux 只是一种对多线程的更好封装=。=
    zeni123
        118
    zeni123  
       2022-04-20 06:08:07 +08:00 via iPhone
    都用 200 个线程池 。。。
    zeni123
        119
    zeni123  
       2022-04-20 06:09:03 +08:00 via iPhone
    就好像在问一公斤铁 一公斤棉那个重
    defage
        120
    defage  
       2022-04-20 08:44:48 +08:00
    lz 不觉得在说“也差不多”,“差不大”的词的时候非常不严谨么。如果你拿普通 crud ,只是做完能跑起来,用这些没问题的。
    本来就在论严谨的问题,放在当下内存动不动就是 32G 的时代,当然可以说都差不多了咯。
    rahuahua
        121
    rahuahua  
       2022-04-20 09:28:44 +08:00
    @jeesk CPU 密集型场景来测试 IO 模型的优略性,这是何苦呢
    rahuahua
        122
    rahuahua  
       2022-04-20 09:32:58 +08:00
    楼主可能也没经历过所谓的高并发,大牛搞出 NIO ,各种协程模型并不是闲的蛋疼,是真的因为需要,早些年还在讨论 C10K , 现在都在讨论 C1000K 是技术在进步。 不要觉得别人都是小白,这种心态不好
    nothingistrue
        123
    nothingistrue  
       2022-04-20 09:43:58 +08:00
    发现楼上有些人是 “nio 优于 bio”,或者 “nio 要替代 bio” 的想法,这是不正确的。NIO 异步非阻塞,BIO 同步阻塞,这俩是应对不同场景的,不是不考虑场景就无脑用一个。如果是客户端应用读取一个文件准备编辑的场景,用 NIO 就是个沙雕(这里 UI 那里可以将读取文件的过程作为子过程异步调用,但读取文件这个过程,单线程阻塞是最优解)。

    还有异步同步也是要分场景的,并且异步本身,也是分为全异步的 callback 模式跟异步同步结合的 async/await 模式。无脑异步,或者无脑 callback ,也是沙雕行为。

    楼主是上面看法的反方,是错的,但不代表上面看法的正方就是对的。楼主的标题说的是很正确的,互联网上的很多知识都会带入非黑即白的世界观,这才是最大的误导。
    SunFarrell
        124
    SunFarrell  
       2022-04-20 09:45:57 +08:00
    所以我们都看开发经验,技术类的文章都知道是花瓶,毕竟国内技术人员写技术文章表达能力不够严谨
    bugFactory
        125
    bugFactory  
       2022-04-20 09:50:42 +08:00
    @MoYi123 不用
    bugFactory
        126
    bugFactory  
       2022-04-20 09:51:05 +08:00
    @Jekins 你的问题加机器就可以解决
    yazinnnn
        127
    yazinnnn  
       2022-04-20 10:13:02 +08:00
    楼主可以把测试的代码或者项目贴出来吗?想看看怎么写的
    akira
        128
    akira  
       2022-04-20 11:17:33 +08:00
    每个人的认知都是不一样的,发出来的内容自然有可能会有偏差。 相比较之下,教科书的基础内容, 大牛的文章 出问题的概率就小很多,可信度较高,最多就是可能有过时。

    总而言之,言而总之,看到一个东西,要自己思考
    lmshl
        129
    lmshl  
       2022-04-20 11:35:02 +08:00
    @nothingistrue nio 确实完全优于 bio ,完全替代这毋庸置疑。2022 年的今天谁在 UI 线程读取文件?十年前都不这么做了好吧。
    如果你真的基础扎实的话,你应该知道 memory hierarchy 中 CPU >> Memory >>> Disk ,即使是计算密集型应用,异步流式文件加载也可以尽可能的让计算过程提前开始,最简单的例子就是“读取 CSV 文件,统计数据”,省下来的 CPU 时间可以留给计算过程,让总计算时间缩的更短。
    jeesk
        130
    jeesk  
    OP
       2022-04-20 12:00:17 +08:00 via Android
    @yazinnnn 代码是去年还是前年测试的, 那时候从运费同事借的测试机玩, 代码早就没了。
    jeesk
        131
    jeesk  
    OP
       2022-04-20 12:03:34 +08:00 via Android
    @defage 你这样扛, 我是默认忽略的。 有几家公司,有多少服务比例直接上 32g ?
    jeesk
        132
    jeesk  
    OP
       2022-04-20 12:07:06 +08:00 via Android
    @nothingistrue nio 并不是异步的。只是非阻塞而已。 真正的异步是 aio 。
    jeesk
        133
    jeesk  
    OP
       2022-04-20 12:10:26 +08:00 via Android
    @yazinnnn https://www.jianshu.com/p/77e8b64ab710 测试的数据在这里, 不过没有内存的图而已。
    jeesk
        134
    jeesk  
    OP
       2022-04-20 12:12:28 +08:00 via Android
    @rahuahua 我说我们生产是有一部分是密集应用。 我什么时候说过用 cpu 密集应用测试过? 又来倒打一把?
    jeesk
        135
    jeesk  
    OP
       2022-04-20 12:17:36 +08:00 via Android
    @lmshl 你说得不对, 阻塞会线程让出 cpu 的。ui 线程不可能做耗时的操作的,这样 ui 线程会卡死。 异步处理就行了。 用这个例子证明 nio 和 bio 不太好。
    lmshl
        136
    lmshl  
       2022-04-20 12:21:29 +08:00
    @jeesk “ui 线程不可能做耗时的操作的,这样 ui 线程会卡死。” 我说的和你说的不是一回事吗?
    JavaScript 这么多年不就是这么处理的么?
    而且 NIO 本身就是异步了
    jeesk
        137
    jeesk  
    OP
       2022-04-20 12:38:41 +08:00 via Android
    @lmshl 浏览器不是单个线程,交互和渲染是分开的。 我说的 ui 可以用安卓线程来举例的。
    liuhan907
        138
    liuhan907  
       2022-04-20 12:39:08 +08:00
    @lmshl NIO 是同步非阻塞,并不是异步。
    lmshl
        139
    lmshl  
       2022-04-20 12:56:44 +08:00
    @jeesk 但浏览器的 JS 是单线程的,交互是不分开的。Android/Winform/WPF 你也可以用 kotlin flow API ,和 C# async/await ,并没有什么不同。过去开新线程发信号,走 delegate ,今天纯异步 fiber
    jeesk
        140
    jeesk  
    OP
       2022-04-20 13:16:44 +08:00 via Android
    @lmshl 单啥? 如果是单线程还能异步请求?
    liuhan907
        141
    liuhan907  
       2022-04-20 13:41:48 +08:00
    @jeesk 浏览器的 js 一直都是单线程的,你总不能说 worker 算多线程吧(
    nothingistrue
        142
    nothingistrue  
       2022-04-20 14:01:32 +08:00
    @lmshl 客户端应用读取一个文件准备编辑,说直白点就是记事本,这你也要用 NIO 去读取文件吗。虽然 Java 桌面应用很拉跨,但你也不能假定 Java 就只是搞 Web 服务器或者大型计算的,有些场景就是简简单单的线性同步处理,这时候你用 NIO 那就是杀鸡用牛刀。

    @jeesk 同步异步这里我草率了,NIO 是确实是同步非阻塞的。NIO 跟 BIO 区别的是任务切换的效率,跟同步异步没多大关系,BIO 也能做异步。他俩用哪个,主要还得看并发。
    nothingistrue
        143
    nothingistrue  
       2022-04-20 14:03:40 +08:00
    @liuhan907 @lmshl 异步跟单线程不冲突,用线程池处理异步是 Java 的专用方式,其他语言处理异步不是用的线程池。
    liuhan907
        144
    liuhan907  
       2022-04-20 14:10:45 +08:00
    @nothingistrue 异步和同步和你用什么处理没关系。准备读取数据的时候数据已经复制到用户缓冲就是异步,否则是同步。线程池那些都是后话。
    defage
        145
    defage  
       2022-04-20 14:19:31 +08:00
    @jeesk 我这是杠。行吧。
    32G 我笔记本都有 32G 呢。
    lmshl
        146
    lmshl  
       2022-04-20 15:07:20 +08:00
    @nothingistrue 还真都用异步 /nio 去读区的,Android 有 kotlin flow ,compose-jb 给搬到桌面端来了,无非就是多个 suspend / await 的事。而且我 Electron 压根就不可能用同步方法去读文件好吧,直接把 UI 卡白屏了,谁不是写个异步 /nio 读取呢。

    只要你习惯了这事,就跟呼吸一样自然,说白了还是写的少了。
    nothingistrue
        147
    nothingistrue  
       2022-04-20 15:11:06 +08:00
    @lmshl 你把响应式处理 /流式处理,跟 NIO 搞混了吧。
    Joker123456789
        148
    Joker123456789  
       2022-04-20 16:28:35 +08:00   ❤️ 1
    我觉得你需要补充一下 ,TCP, HTTP 协议的相关知识。 搞清楚,线程是在什么时候开的,什么时候结束的。

    我用大白话说一下吧:

    BIO 是打电话,如果同时很多人打进来 你就需要很多的手机 来处理。 并且每个电话接通后 你都必须完整的处理成功后才能挂断( BIO 就是必须处理完成才能挂断,千万别跟我杠 你可以先挂断 去接别的电话,因为这是不可以的), 你自己脑补一下,电话多了以后,接电话的人有多崩溃。

    NIO 是发信息,如果同时很多人找你,你只是会收到很多信息而已,一部手机就可以处理了,而且你可以自己阅读信息的内容,来决定 先处理哪个,后处理哪个。

    这么说,你应该有点概念了吧?

    我再详细一点:

    BIO, 电话接通后,那头跟你说:我这边有一份文件,我念给你听哦, 然后 你拿起纸笔,一边听,一边写, 在他念完之前 你都必须 一直听着,即使信号不好 导致断断续续的,即使他念了几句,跑去上厕所了,又或者 他故意 一段一段的念给你听,每段之间 都要去上一次厕所,或者吃一次零食。 你都必须 老老实实的 拿着电话在这等,不能处理别的, 也就是说你一直被占线了。

    NIO ,那头 给你发的短信有多少,你就写多少,写完了就去看下条短信,完全不用卡在这等这个人,如果没有短信进来,你还可以休息一会。

    这么说 是不是又更清楚了??

    在网络通讯中,服务端需要接收到一个完整的报文,才能交给应用层去处理,也就是说,你必须要 等电话那头的人把一整段文件念给你听,并且你已经全部写在纸上了,才能交给应用层。

    你在你的题目中描述的场景,他不属于 NIO ,也不属于 BIO ,他是个纯应用层的场景, 他是发生在 接电话,或者读短信的那个人,把电话那头的人要发的文件全部写完之后,才发生的事。 这个层面 其实用什么 IO 都是一样的,

    但是你现在好像只有这一层的概念,对 IO 那一层完全没概念,所以出现错误的结论 也不是稀奇, 再继续加油吧,以后学深一点再来质疑。
    Joker123456789
        149
    Joker123456789  
       2022-04-20 16:49:13 +08:00
    @nothingistrue NIO 是同步非阻塞, 而且 IO 最大的应用场景 是 做网络通讯,不是读写文件。
    araaaa
        150
    araaaa  
       2022-04-20 17:35:33 +08:00
    当你有几万条连接需要同时调度的时候就知道非阻塞的用处了
    documentzhangx66
        151
    documentzhangx66  
       2022-04-20 17:41:02 +08:00
    建议先玩一下 Factorio 这款游戏,以可视化的方式展示这类问题。
    aguesuka
        152
    aguesuka  
       2022-04-21 11:56:01 +08:00
    楼主还是表达有问题, 你直接贴知乎答案

    ```
    假如有 10000 个连接,4 核 CPU ,那么 bio 就需要一万个线程,而 nio 大概就需要 5 个线程(一个接收请求,四个处理请求)。如果这 10000 个连接同时请求,那么 bio 就有 10000 个线程抢四个 CPU ,几乎每个 CPU 平均执行 2500 次上下文切换,而 nio 四个处理线程,几乎每个线程都对应一个 CPU ,也就是几乎没有上下文切换。效率就体现出来了。

    链接: https://www.zhihu.com/question/59356897/answer/164387902
    ```

    当然是错误的

    ```
    说下我的理解哈:
    前提:BIO 面向流,NIO 面向缓冲区
    10000 个连接,4 核 CPU ,如果是 BIO ,那么 Client ->OS 这个过程是阻塞的,当其中 4 个线程获取到 CPU 等待 OS 读取资源,那么剩余 9996 个线程就阻塞等待 T1,假设 1 个线程等待 OS 读取资源需要阻塞 1s(其他耗时先不考虑哈),那么剩下的线程就需 9996/4s ,这就可怕了。
    而 NIO 呢,当其中 4 个线程获取到 CPU 资源去缓冲区读取资源,发现 OS 还没把资源放到缓冲区中,就释放 CPU 资源去干其他事,让其他线程来试试。这就节省了阻塞时间。当然了,如果线程发现缓冲区有准备好的数据的时候,效率和 BIO 还是一样的,其他线程还是要等这 4 个线程读取完数据的(同步)。

    个人理解,有偏差了还请大佬帮忙指正下。
    ```

    楼下的小白就给误导了
    1  2  
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2841 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 09:53 · PVG 17:53 · LAX 02:53 · JFK 05:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.