• 关于我对js运行时的一些浅薄的理解:(2,给我们的js异步的能力,eventloop)


    quickjs 究竟是什么

    上一篇讲到这个轮子本质上其实就是一个编译器了。或者说是一个独立的js引擎。通过混入原生环境的方式大幅提高性能!当然了,也可以看作运行时,因为已经是一个可以进行系统调用的平台了
    QuickJS 出自传奇程序员 Fabrice Bellard(膜拜)

    给我们的js实现简单的异步

    异步并非天生就有,计算机执行代码只有同步。所谓的异步无非就是用同步去模拟异步。一个很经典的思路是轮询(poll)
    poll这个词在操作系统很常见,本质上就是通过等待队列实现异步。

    前置知识 轮询和中断(不懂的建议复习操作系统)

    quickjs中settimeout实现

    虽然我们几乎每天都用到settimeout这api,但js引擎并没有这个api,这个api是每个运行时自己实现的,q浏览器和nodejs本身都实现了自己的setimeout

    import { setTimeout } from "os";
    
    setTimeout(() => { /* ... */ }, 0);
    

    这就是在quickjs中调用settimeout的方式,和之前的fib并没啥区别

    static JSValue js_os_setTimeout(JSContext *ctx, JSValueConst this_val,
                                   int argc, JSValueConst *argv)
    {
       int64_t delay;
       JSValueConst func;
       JSOSTimer *th;
       JSValue obj;
    
       func = argv[0];
       if (!JS_IsFunction(ctx, func))
           return JS_ThrowTypeError(ctx, "not a function");
       if (JS_ToInt64(ctx, &delay, argv[1]))
           return JS_EXCEPTION;
       obj = JS_NewObjectClass(ctx, js_os_timer_class_id);
       if (JS_IsException(obj))
           return obj;
       th = js_mallocz(ctx, sizeof(*th));
       if (!th) {
           JS_FreeValue(ctx, obj);
           return JS_EXCEPTION;
       }
       th->has_object = TRUE;
       th->timeout = get_time_ms() + delay;
       th->func = JS_DupValue(ctx, func);
       list_add_tail(&th->link, &os_timers);
       JS_SetOpaque(obj, th);
       return obj;
    }
    

    源码很简单,就是可以看出,这个 setTimeout 的实现中,并没有任何多线程或 poll 的操作,只是把一个存储 timer 信息的结构体通过 JS_SetOpaque 的方式,挂到了最后返回的 JS 对象上而已,是个非常简单的同步操作。因此,就和调用原生 fib 函数一样地,在 eval 执行 JS 代码时,遇到 setTimeout 后也是同步地执行一点 C 代码后就立刻返回,没有什么特别之处。
    为什么能异步,可以翻看我之前的那个文章实现fib时候的代码,很简单,main函数里调用了loop()

    int main(int argc, char **argv)
    {
      // ...
      // eval JS 字节码
      js_std_eval_binary(ctx, qjsc_hello, qjsc_hello_size, 0);
      // 启动 Event Loop
      js_std_loop(ctx);
      // ...
    }
    

    而loop实现也很简单

    /* main loop which calls the user JS callbacks */
    void js_std_loop(JSContext *ctx)
    {
        JSContext *ctx1;
        int err;
    
        for(;;) {
            /*在这里执行pending操作*/
            for(;;) {
                err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
                if (err <= 0) {
                    if (err < 0) {
                        js_std_dump_error(ctx1);
                    }
                    break;
                }
            }
    
            if (!os_poll_func || os_poll_func(ctx))
                break;
        }
    }
    

    和网络上所有的evetloop博客写的一样,双重无限循环,先执行所有的回调,然后在进行os_poll_func
    这里的os_poll_func
    就是原理类似的 poll 系统调用,从而可以借助操作系统的能力,使得只在【定时器触发、文件描述符读写】等事件发生时,让进程回到前台执行一个 tick,把此时应该运行的 JS 回调跑一遍,而其余时间都在后台挂起。在这条路上继续走下去,就能以经典的异步非阻塞方式来实现整个运行时啦。

  • 相关阅读:
    JVM内存模型
    生产者与消费者的Java实现
    kafka常用命令
    java中join用法
    elasticsearch关于索引切分的实现
    三十六进制加法
    leetCode之Median of Two Sorted Arrays
    腾讯云“动态加速”与“CDN”的区别——浅谈对“动态加速”的理解(可能有误)
    洗澡或游泳等导致的耳朵进水的解决方案
    windows服务器间文件同步搭建步骤搜集
  • 原文地址:https://www.cnblogs.com/geck/p/13664370.html
Copyright © 2020-2023  润新知