Live2D

关于我对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 回调跑一遍,而其余时间都在后台挂起。在这条路上继续走下去,就能以经典的异步非阻塞方式来实现整个运行时啦。

posted @ 2020-09-14 01:23  二胖he鼠标  阅读(535)  评论(0编辑  收藏  举报