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

请教一下, nodejs/express 是如何处理多个请求的

  •  1
     
  •   coldmonkeybit · 2022-11-16 10:55:35 +08:00 · 3500 次点击
    这是一个创建于 746 天前的主题,其中的信息可能已经有所发展或是发生改变。

    就是没有太理解这一点,虽然知道 IO 操作通过 async/await 异步执行的话不会阻塞主线程运行,但是如果有多个请求的话 node 是如何处理的呢,就是请求本身是否会被异步处理吗,还是说同一时间,只会有一个请求被处理,直到遇到 async/await ,如果有其他请求的话,主线程才会处理这些请求。
    由于对并发问题不是特别了解,请各位大佬指教一下。

    23 条回复    2022-11-17 10:15:17 +08:00
    Maboroshii
        1
    Maboroshii  
       2022-11-16 11:08:01 +08:00 via Android   ❤️ 1
    单线程程序同一时刻只能执行一行代码,await 可以让出 cpu 去执行其他的代码,等 await 有结果了,并且抢到 cpu 了,就回来继续执行。这是我的理解。
    coldmonkeybit
        2
    coldmonkeybit  
    OP
       2022-11-16 11:15:26 +08:00
    @Maboroshii 大概明白了,同一时刻只能执行一行代码,就意味着每次只能执行一个请求,当遇到 await 让出 cpu 之后,才能执行第二个请求,如果想要同时处理多个请求,应该就需要用子进程之类的处理方式吧。
    star7th
        3
    star7th  
       2022-11-16 11:26:45 +08:00
    我没有认真去钻研底层,大多情况下只是一个使用者。但根据我对 nodejs 使用经验,大概是:
    在同一个进程里,请求确实是异步处理的。await 挂起一个后,cpu 会处理另一个。但由于很快,所以你可以理解为“接近是同时处理的” 。对不同的两个用户来讲,同时访问 node 接口并没有什么明显延迟感觉 。
    如果你是追求非常高的”同时“,那可以多进程处理。比如 eggjs 就可以起多个 work 进程。
    好像新出一种特性,可以更细粒度在单进程里模拟多进程,减少进程切换成本。但是没太细了解。总之,如果你要追求高度同步处理,就起多个进程就行。
    tux
        4
    tux  
       2022-11-16 11:27:05 +08:00
    不用子进程,正是异步的强大的地方,子进程多了,开销就大,异步没这方面开销,一个一个处理,效率高
    wangtian2020
        5
    wangtian2020  
       2022-11-16 11:27:29 +08:00
    不要阻塞,指的是不要使用
    名字带 Sync 的方法,比如
    fs.writeFileSync
    fs.readFileSync
    因为他们会直接卡死主线程,直到结果返回,假如读文件花 1 秒钟,那这一秒钟内任何其他请求都会等待
    不阻塞用老的回调方法,或者新的 promise 方法都行 比如 fs.promises.writeFile

    举个例子,你写一个请求,读取本地 txt 文件,假如要花一秒钟。如果用非同步 Sync 的方法,那就是 nodejs 告诉系统,你去读这个文件,读完了通知我,我来执行回调。同步就是一直死等,其他事啥也不做。
    https://www.zhihu.com/question/62254462/answer/1611867539

    同时处理多个请求,不要用阻塞方法就行了,跟子进程没关系。除非你说的请求是,一个循环 1000 万遍的方法,那样真会卡 1 秒钟。
    ysc3839
        6
    ysc3839  
       2022-11-16 11:42:00 +08:00
    你需要知道整个程序大致的逻辑是这样的:
    while (true) {
    const 事件 = 等待事件();
    if (事件) 事件.处理函数();
    }
    当有请求来了,就会调用处理请求的函数,直到函数返回,才会等待并处理下一个事件。

    假如你的函数中 await 了别的异步事件,比如这样:
    async function() {
    await sleep(10);
    send('ok');
    }
    可以把 async function 看成回调函数的语法糖,实际的逻辑类似:
    function() {
    sleep(10, function() {
    send('ok');
    });
    }
    await 时当前函数就返回了,继续等待事件,此时就可以处理其他请求了。sleep 完成后也会有个 sleep 完成的事件,会调用传递给 sleep 的回调函数,看上去就是 await 完成了继续执行。
    no13bus
        7
    no13bus  
       2022-11-16 11:59:57 +08:00
    @star7th 协程?
    star7th
        8
    star7th  
       2022-11-16 12:06:18 +08:00
    himself65
        9
    himself65  
       2022-11-16 12:16:50 +08:00 via iPhone   ❤️ 4
    作为 nodejs member 补充一下个人看法,nodejs 底层是包装了 libuv ,对于用户(开发者)来说是单线程,但是底层可能会给不同的线程处理你的异步请求(比如读文件)

    比如随时找了个文章: https://www.digitalocean.com/community/tutorials/how-to-use-multithreading-in-node-js#
    otakustay
        10
    otakustay  
       2022-11-16 12:39:45 +08:00
    不用 cluster 的话,请求里的代码逻辑依然是单线程的,即一个请求在 CPU 处理(非 IO )时,另一个请求要 CPU 是会卡住的
    okakuyang
        11
    okakuyang  
       2022-11-16 13:01:14 +08:00
    请求可以接收很多,但是只能一个个处理,因为 node 是单线程。也可以用 node 的多线程,这样看起来就像有多个 node 分别处理请求。
    dcsuibian
        12
    dcsuibian  
       2022-11-16 13:06:28 +08:00   ❤️ 1
    就是单线程的,每个时刻只能处理一个请求。(不包括 worker )

    但关键在于,CPU 的速度远远快于 IO ,时间往往浪费在 IO 等待而不是程序运行上。
    按我的理解,Nodejs 就是把这种 IO 操作交给了底层,底层可以是多线程的,总之你别管。

    所以如果是 IO 密集型应用,那么 Nodejs 是非常合适的,但对于计算密集型应用,就确实会卡住了。
    ochatokori
        13
    ochatokori  
       2022-11-16 13:21:15 +08:00 via Android
    请求对 node 来说也是异步的 io
    cctv1005s927
        14
    cctv1005s927  
       2022-11-16 16:56:34 +08:00
    Linux 上是 epoll 啦: https://kaleid-liner.github.io/blog/2019/06/02/epoll-web-server.html

    再进入 internal function 之前的所有 JS 代码都在单线程上跑着,然后进入 internal function 之后就把 http 请求交给 epoll 去处理了。
    dudubaba
        15
    dudubaba  
       2022-11-16 17:09:20 +08:00   ❤️ 1
    请求是异步,执行是同步。假如 1000 个请求一起进来,然后代码里给个超时任务,那所有的请求都会被挂起。所以 node 里一般不做同步操作磁盘或者计算等操作。
    Jamy
        16
    Jamy  
       2022-11-16 17:12:47 +08:00   ❤️ 1
    执行 js 代码的线程是只有一个,所有需要 js 处理的操作都会进入一个队列,node 按照特定的规则处理队列数据,
    当涉及到 io 时会使用操作系统提供的 io 复用接口或者新开线程特殊处理,处理完成再把结果扔回队列
    nielinjie
        17
    nielinjie  
       2022-11-16 17:19:05 +08:00
    楼主要搞清楚的是单线程 /多线程、同步 /异步、阻塞 /非阻塞这几个概念。这几个有关系但不相同。
    Yeen
        18
    Yeen  
       2022-11-16 17:28:03 +08:00
    首先要清楚,nodejs 也是基于 js 语言模式,因此主线程也是 event loop 的。
    网上讲这个资料的很多可搜一下。

    每次由事件触发一次处理,进入 /退出事件处理代码(就是用户代码)。

    async/await 无非就是某次进入 event loop 的代码执行显式的停留在等待 promise 的 resovle/reject 的调用点,直到 promise 已决才继续往下执行,注意只是代码执行停留,但线程不阻塞。此时主线程完全可能进入其他的 event loop 处理,(比如其他地方定时器唤醒),因此是异步的。
    coolloves
        19
    coolloves  
       2022-11-16 17:37:30 +08:00
    cy 慢慢看
    KouShuiYu
        20
    KouShuiYu  
       2022-11-16 21:16:18 +08:00
    我的理解只要不是调用了同步方法各种 xxxSync 或者 CPU 在一直计算比如从 1 循环加到一亿,都可以

    你可以试试
    先访问 http://127.0.0.1:3000/?t=10000 再访问 http://127.0.0.1:3000/?t=0 第一次结果还没返回,第二次结果是秒回的

    const http = require('http');
    const { URL } = require('url');
    const { setTimeout } = require('timers/promises');

    const hostname = '127.0.0.1';
    const port = 3000;

    const server = http.createServer(async (req, res) => {
    const t = new URL(`http://${hostname}:${port}${req.url}`).searchParams.get('t');
    // 模拟耗时操作
    await setTimeout(t);

    res.end(`Hello World:${t}`);
    });

    server.listen(port, hostname, () => {
    console.log(`Server running at http://${hostname}:${port}`);
    });
    joesonw
        21
    joesonw  
       2022-11-16 21:42:08 +08:00 via iPhone
    @KouShuiYu setTimeout 的时候就已经让出主线程了。
    XCFOX
        22
    XCFOX  
       2022-11-17 00:01:40 +08:00
    DICK23
        23
    DICK23  
       2022-11-17 10:15:17 +08:00
    异步单线程,各种任务处理可以看成是异步任务的注册,等任务完成再通知主线程进行后续处理
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1022 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 18:52 · PVG 02:52 · LAX 10:52 · JFK 13:52
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.