Dynamic Module 可以实现异步 API 吗?

比如用 libcurl 实现一个类似 url-retrieve 的 API?有可能吗?有的话,怎么做?

据我所知 url-retrieve 貌似是用异步子进程 + process filter 实现的。

Emacs 28 加了个

int open_channel (emacs_env *ENV, emacs_value PIPE_PROCESS)

不清楚能不能派上用场。

如果有这个就可以做一个特别流畅的补全前端了。

但是使用起来可能不那么方便。 elisp 代码估计没有办法放到异步的线程里面。

该接口跟子进程差不多, 给emacs发数据还是文本或者字节流格式, emacs那边需要解析或转换为lisp, 效率较低.

如果能异步构造lisp对象并发送就好了, 这才算真正的异步.

我最期望的方式是类似于web worker的方式, 搞个lisp worker. 这才是真正的异步并发.

只有IO操作可以异步,IO操作怎么给你制造Lisp Object的机会?你要的不是所谓“真正的异步”。就是多线程的并行,是一种并发,但不是异步

MDN里好几处都明示暗示所谓的Worker就是开一个线程执行代码

回到Lisp Worker上来,除了Emacs自己的线程机制(因为有GIL其实并不高效),Rust自己开几个线程执行任务完全可行。之所以我的简介里没讲,是因为并发编程里头的门道太多,太复杂了。简单讲讲等于没讲,要是摊开了讲,那就喧宾夺主,文章主次分不清了。


Worker 接口会生成真正的操作系统级别的线程,如果你不太小心,那么并发会对你的代码产生有趣的影响。然而,对于 web worker 来说,与其他线程的通信点会被很小心的控制,这意味着你很难引起并发问题。你没有办法去访问非线程安全的组件或者是 DOM,此外你还需要通过序列化对象来与线程交互特定的数据。所以你要是不费点劲儿,还真搞不出错误来。

所以这个所谓Worker就是一种改进的,基于thread而非process的 emacs-async,传递对象同样需要序列化/反序列化。不过emacs async没实现消息收发机制,只能一个form从头执行到尾。

Elisp最大的问题是没有多线程并发支持,和子进程异步还不是一个概念。

有多线程的好处是,即使Elisp再慢,只是结果计算慢,但不会因为大数据处理卡界面。

make-thread添加了好长时间了,不过利用这个特性的包,还是不多的

Emacs 的 make-thread 就是一个假的线程,根本不是多线程并发。当然没人用。

好像听说有限制,不过细节不太了解。。。

open_channel 好像可行,Module 里创建一个 Thread,给 libcurl 用,然后立即返回:

static emacs_value
curl (emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data)
{
  int fd = env->open_channel (env, args[0]);
  FILE *stream = fdopen (fd, "w");
  pthread_t thread;
  pthread_create (&thread, NULL, curl_do_work, stream);
  return env->intern (env, "nil");
}
(curl (make-pipe-process ...))

但问题是不知道这个 process 什么时候该结束,libcurl 是知道,但是没有机会传回 Emacs 了,因为 module 函数早就返回了。依赖 Content-Length 好像也不太可靠?

传callback

不太明白是什么意思? 好像没提到仅IO异步. 我的意思是可以在非lisp线程里构造lisp对象. 然后发给emacs主线程使用.

LZ提的新接口可以让模块线程跟emacs主线程通信, 但是这种通信还是类似于子进程的模式, 就是发文本或原始二进制流, 而不能发送对象, 所以效率不高. 如果能在模块线程里"异步"构造lisp对象(现在是不支持的)传递给emacs主线程, 意义就不一样了.

这没什么问题啊. 我要的就是这种效果. 据我的理解, web worker是可以真正并发的, 就是在一个worker里搞一个while(1)循环, 不会让其他worker或者主线程卡住.

因为emacs的elisp跟浏览器的js很像, 用户交互也很像, 两者都需要快速的界面响应, 而浏览器由于人手和资源丰富, 这些年做过很多探索, 现在体验已经相当不错了. emacs完全可以借鉴. 感觉emacs目前的线程功能局限性太大了, 不太看好. web worker方式简单很多.

所以并发和异步是什么关系?

简单来说就是, 一个里面死循环, 另外一个依然可以正常运行.

web worker里的对象都是js对象, 理论上应该不需要额外的序列化和反序列化(没有深入了解), 也就是说传递指针即可, 这样效率就比较高了.

另外, 即使(万一)web worker不能传递对象, 需要序列化和反序列化, 应该只是简单的对象复制(因为两边都是js环境), 这种效率也很高啊. 不可能先转成json, 然后另一边再解析json, 不大可能这样.

那这和 lisp 对象有关系?你一开始直接说传个外部指針不就好了

你说的是 shared memory IPC,除了 C 的 struct 那样的 memory representation 相当简单的,Java 之类有复杂的 reference 的对象都不能直接用 shared memory 传,一样要 serialize。本质上和传个 json 也就 overhead 有多少的区別。

1 个赞

在主页面与 worker 之间传递的数据是通过拷贝,而不是共享来完成的。传递给 worker 的对象需要经过序列化,接下来在另一端还需要反序列化。页面与 worker 不会共享同一个实例,最终的结果就是在每次通信结束时生成了数据的一个副本。大部分浏览器使用结构化拷贝来实现该特性。

MDN说过需要序列化

只是嫌弃Elisp解析JSON慢的话用prin1read来序列化/反序列化 不就行了吗

这样来说, golang要先进太多了, 多个routine(可能在同一个线程, 也可能在不同线程)之间相互传对象或者对象指针轻轻松松, 家常便饭. 他们用的同一个GC. 另外, golang是真并发.

这样还不如用pdumper的序列和反序列化函数呢, 应该比read性能好吧?

go routine 用的 green thread 和 system thread 什么区別你先了解一下。green thread 实际上根本没有跨线程。