Erlang NIF的抢占式调度

描述

Erlang VM(BEAM)最吸引人的特性之一就是它执行代码的方式。Erlang中的所有工作都是在Erlang进程中完成的(不要与OS进程混淆)。它们的设计非常轻巧,这意味着可以同时运行数百万个它们。如果要在Erlang中编写Web应用程序,则可以为每个传入请求启动一个新进程。

这种方式和它一样有效的原因是因为BEAM计划执行不同进程的方式。BEAM确保单个进程在撞到运行队列的后面之前不会运行超过指定的时间,并且允许其他进程运行。对于您的Web应用程序,这意味着一个(或一百个)行为不当的请求不会破坏您的其他请求的延迟和体验。

原生

NIF代表Native Implemented Function,是BEAM提供的用于运行本机代码作为Erlang应用程序一部分的机制之一。NIF方法涉及在C中编写共享库,它导出可以加载到BEAM并被调用的函数。

NIF通常用于通过下拉到本机代码来加速计算,以及将低级库中的有用功能暴露给Erlang。

NIF的一个重要提示是它们与Erlang调度程序在同一个线程中运行。由于Erlang调度程序的调整非常精细,因此它们不会被设计为长时间被阻止。这意味着阻塞超过大约一毫秒的NIF将开始对整个VM的性能,稳定性和延迟产生负面影响。

想法

如果想要仍在同一个线程上运行时并透明地执行本机代码,需要怎么做?需要的是满足这两个条件:

  • 跳出调用堆栈中任意点的方法,执行其他操作,然后重新启动并继续工作
  • 另一种线程在没有合作的情况下中断正在进行的计算的方法

可能解决问题的方法:

  • 实际上需要能够在调用链之前跳转到函数,执行其它一些工作,然后在稍后的时间恢复开始停止了工作。如果只是单一跳过堆栈,后续调用将覆盖堆栈上的数据,这就需要恢复暂停的工作。因此,需要一个单独的堆栈来进行可中断的工作
  • 在linux的posix实现中,找到一个线程来中断另一个线程执行的唯一方法就是信号。为用户信号注册一个信号处理程序,它将在执行时跳回到原先的主栈

方案

  • 手动将计算拆分为块。这里有很多的操作,并且在调用其它库时不起作用
  • Dirty NIFs 。可以手动将NIF标记为dirty,这将使其在“脏调度程序”上运行。这解决了VM稳定性等问题,但性能更差,延迟仍是一个问题。在测试中,需要特殊构建的BEAM
  • Threaded NIFs 。可以手动将计算移动到自己的线程中。这可以解决稳定性,性能和延迟问题,但带来了更多的代码复杂性
  • 端口。使用端口,您可以通过stdin和stdout与外部程序通信