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

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

  •  
  •   coldmonkeybit · 73 天前 · 1888 次点击
    这是一个创建于 73 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    23 条回复    2022-11-17 10:15:17 +08:00
    Maboroshii
        1
    Maboroshii  
       73 天前 via Android   ❤️ 1
    单线程程序同一时刻只能执行一行代码,await 可以让出 cpu 去执行其他的代码,等 await 有结果了,并且抢到 cpu 了,就回来继续执行。这是我的理解。
    coldmonkeybit
        2
    coldmonkeybit  
    OP
       73 天前
    @Maboroshii 大概明白了,同一时刻只能执行一行代码,就意味着每次只能执行一个请求,当遇到 await 让出 cpu 之后,才能执行第二个请求,如果想要同时处理多个请求,应该就需要用子进程之类的处理方式吧。
    star7th
        3
    star7th  
       73 天前
    我没有认真去钻研底层,大多情况下只是一个使用者。但根据我对 nodejs 使用经验,大概是:
    在同一个进程里,请求确实是异步处理的。await 挂起一个后,cpu 会处理另一个。但由于很快,所以你可以理解为“接近是同时处理的” 。对不同的两个用户来讲,同时访问 node 接口并没有什么明显延迟感觉 。
    如果你是追求非常高的”同时“,那可以多进程处理。比如 eggjs 就可以起多个 work 进程。
    好像新出一种特性,可以更细粒度在单进程里模拟多进程,减少进程切换成本。但是没太细了解。总之,如果你要追求高度同步处理,就起多个进程就行。
    tux
        4
    tux  
       73 天前
    不用子进程,正是异步的强大的地方,子进程多了,开销就大,异步没这方面开销,一个一个处理,效率高
    wangtian2020
        5
    wangtian2020  
       73 天前
    不要阻塞,指的是不要使用
    名字带 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  
       73 天前
    你需要知道整个程序大致的逻辑是这样的:
    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  
       73 天前
    @star7th 协程?
    star7th
        8
    star7th  
       73 天前
    himself65
        9
    himself65  
       73 天前 via iPhone   ❤️ 4
    作为 nodejs member 补充一下个人看法,nodejs 底层是包装了 libuv ,对于用户(开发者)来说是单线程,但是底层可能会给不同的线程处理你的异步请求(比如读文件)

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

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

    所以如果是 IO 密集型应用,那么 Nodejs 是非常合适的,但对于计算密集型应用,就确实会卡住了。
    ochatokori
        13
    ochatokori  
       73 天前 via Android
    请求对 node 来说也是异步的 io
    cctv1005s927
        14
    cctv1005s927  
       72 天前
    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  
       72 天前   ❤️ 1
    请求是异步,执行是同步。假如 1000 个请求一起进来,然后代码里给个超时任务,那所有的请求都会被挂起。所以 node 里一般不做同步操作磁盘或者计算等操作。
    Jamy
        16
    Jamy  
       72 天前   ❤️ 1
    执行 js 代码的线程是只有一个,所有需要 js 处理的操作都会进入一个队列,node 按照特定的规则处理队列数据,
    当涉及到 io 时会使用操作系统提供的 io 复用接口或者新开线程特殊处理,处理完成再把结果扔回队列
    nielinjie
        17
    nielinjie  
       72 天前
    楼主要搞清楚的是单线程 /多线程、同步 /异步、阻塞 /非阻塞这几个概念。这几个有关系但不相同。
    Yeen
        18
    Yeen  
       72 天前
    首先要清楚,nodejs 也是基于 js 语言模式,因此主线程也是 event loop 的。
    网上讲这个资料的很多可搜一下。

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

    async/await 无非就是某次进入 event loop 的代码执行显式的停留在等待 promise 的 resovle/reject 的调用点,直到 promise 已决才继续往下执行,注意只是代码执行停留,但线程不阻塞。此时主线程完全可能进入其他的 event loop 处理,(比如其他地方定时器唤醒),因此是异步的。
    coolloves
        19
    coolloves  
       72 天前
    cy 慢慢看
    KouShuiYu
        20
    KouShuiYu  
       72 天前
    我的理解只要不是调用了同步方法各种 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  
       72 天前 via iPhone
    @KouShuiYu setTimeout 的时候就已经让出主线程了。
    XCFOX
        22
    XCFOX  
       72 天前
    DICK23
        23
    DICK23  
       72 天前
    异步单线程,各种任务处理可以看成是异步任务的注册,等任务完成再通知主线程进行后续处理
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   实用小工具   ·   2543 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 90ms · UTC 06:23 · PVG 14:23 · LAX 22:23 · JFK 01:23
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.