【问题标题】:Erlang spawning large amounts of C processesErlang 产生大量 C 进程
【发布时间】:2015-08-22 03:34:47
【问题描述】:

我一直在研究如何在 Erlang 中嵌入语言(我们以 Lua 为例)。这当然不是一个新想法,并且有很多图书馆可以做到这一点。但是我想知道是否可以启动一个由 Lua 修改的状态的 Genserver。这意味着一旦您启动 Genserver,它将启动一个(长时间运行的)Lua 进程来操作 Genserver 的状态。我知道这也是可能的,但我想知道我是否可以生成 1,000 10,000 甚至 100,000 个这样的进程。

我对这个话题不是很熟悉,但我做了一些研究。 (如果我对这些选项中的任何一个有误,请纠正我)。

TLDR;跳到最后一段。

第一个选项:NIF:

这似乎不是一个选项,因为它会阻止当前进程的 Erlang 调度程序。如果我想生成大量这些,它将冻结整个运行时。

第二个选项:端口驱动:

它类似于NIF,但通过向指定端口发送数据进行通信,该端口也可以将数据发送回Erlang。这很好,尽管这似乎也阻止了调度程序。我尝试了一个库,它也为你做样板,但这似乎在产生 10 个进程后阻塞了调度程序。我还查看了 Erlang 文档中的 postgresql 示例,据说它是异步的,但我无法让示例代码正常工作(R13?)。是否有可能在不阻塞运行时的情况下运行尽可能多的端口驱动程序进程?

第三个选项:C 节点:

我认为这很有趣并想尝试一下,但显然“erlang-lua”项目已经这样做了。这很好,因为如果出现问题并且进程被隔离,它不会使您的 Erlang VM 崩溃。但是为了真正产生一个进程,你需要产生一个完整的节点。我不知道这有多贵。我也不确定连接集群中的节点的限制是什么,但我没有看到自己产生 100,000 个 C 节点。

第四个选项:端口:

起初我以为这与端口驱动程序相同,但实际上有所不同。您生成一个执行应用程序并通过 STDIN 和 STDOUT 进行通信的进程。这对于生成大量进程非常有效,而且(我认为?)它们不会对 Erlang VM 构成威胁。但是,如果我要通过 STDIN / STDOUT 进行通信,为什么还要从一开始就使用嵌入式语言呢?不妨使用任何其他脚本语言。

因此,在我不熟悉的领域进行了大量研究之后,我来到了这里。您可以将 Genserver 作为 AI 用 Lua 编写的“实体”。这就是为什么我想为每个实体都有一个流程。我的问题是如何生成许多与长期运行的 Lua 进程通信的 Genserver?这甚至可能吗?我应该以不同的方式解决我的问题吗?

【问题讨论】:

  • Lua代码真的需要完全接管底层内核线程吗?或者它是否可以设置为可以注册某种回调或通知,以便它可以将内核线程返回给 Erlang,然后在它感兴趣的事件发生时被回调?
  • 在 Lua 的特定情况下,您可能想查看 github.com/rvirding/luerl,它是 Erlang 中的 Lua 实现。您只需在 erlang 进程中加载​​和执行 Lua 代码(这样您就可以拥有许多并发的 lua 评估器)。

标签: c multithreading lua erlang ffi


【解决方案1】:

如果你可以让 Lua 代码——或者更准确地说,它的底层原生代码——与 Erlang VM 配合,你有几个选择。

考虑 Erlang VM 最重要的功能之一:跨相对较小的调度程序线程集管理(可能大量)Erlang 轻量级进程的执行。它使用多种技术来了解进程何时用完其时间片或正在等待,因此应将其安排出来,以便让另一个进程有机会运行。

您似乎在问如何让本机代码在 VM 中随心所欲地运行,但正如您已经暗示的那样,本机代码可能导致 VM 出现问题的原因是它没有实用的方法来停止本机代码完全接管调度程序线程,从而阻止常规 Erlang 进程执行。因此,本机代码必须协同地将调度程序线程让给 VM。

对于较旧的 NIF,此类合作的选择是:

  1. 将 NIF 调用在调度程序线程上运行的时间保持在 1 毫秒或更短。
  2. 创建一个或多个私有线程。将每个长时间运行的 NIF 调用从其调度程序线程转移到专用线程执行,然后将调度程序线程返回给 VM。

这里的问题是,并非所有调用都能在 1 毫秒或更短的时间内完成,并且管理私有线程可能容易出错。为了解决第一个问题,一些开发人员会将工作分解成块,并使用 Erlang 函数作为包装器来管理一系列简短的 NIF 调用,每个调用完成一个工作块。至于第二个问题,好吧,有时你就是无法避免,尽管它本身就很困难。

在 Erlang 17.3 或更高版本上运行的 NIF 也可以使用 enif_schedule_nif function 协同产生调度程序线程。要使用此功能,本机代码必须能够以块的形式完成其工作,以便每个块可以在通常的 1 毫秒 NIF 执行窗口内完成,类似于前面提到的方法,但不需要人为地返回到 Erlang 包装器。我的bitwise example code 提供了很多关于此的详细信息。

Erlang 17 还带来了一个实验性功能,默认情况下是关闭的,称为 dirty schedulers。这是一组 VM 调度程序,与常规调度程序没有相同的本机代码执行时间限制;在那里工作基本上可以无限期地阻塞,而不会中断正常的虚拟机操作。

脏调度程序有两种形式:用于 CPU 密集型工作的 CPU 调度器和用于 I/O 密集型工作的 I/O 调度器。在为启用脏调度程序而编译的 VM 中,默认情况下,脏 CPU 调度程序的数量与常规调度程序一样多,并且有 10 个 I/O 调度程序。可以使用命令行开关更改这些数字,但请注意,要尝试防止常规调度程序饥饿,您永远不能拥有比常规调度程序更多的脏 CPU 调度程序。应用程序使用前面提到的相同的enif_schedule_nif 函数在脏调度程序上执行NIF。我的bitwise example code 也提供了许多有关此的详细信息。脏调度器仍将是 Erlang 18 的实验性功能。

链接端口驱动程序中的本机代码受到与 NIF 相同的调度程序执行时间限制,但驱动程序具有 NIF 不具备的两个功能:

  1. 驱动程序代码可以将文件描述符注册到 VM 轮询子系统中,并在这些文件描述符中的任何一个变为 I/O 就绪时收到通知。
  2. 驱动 API 支持访问非调度程序异步线程池,其大小可配置,但默认情况下有 10 个线程。

第一个功能允许本机驱动程序代码避免阻塞 I/O 线程。例如,驱动程序代码可以注册套接字文件描述符,而不是执行阻塞的recv 调用,以便 VM 可以轮询它并在文件描述符变得可读时回调驱动程序。

第二个特性提供了一个单独的线程池,用于不符合调度程序线程本机代码执行时间限制的驱动程序任务。您可以在 NIF 中实现相同的功能,但您必须设置自己的线程池并编写自己的本机代码来管理和访问它。但无论你使用驱动程序异步线程池、你自己的 NIF 线程池还是脏调度程序,请注意它们都是常规的操作系统线程,因此尝试启动大量它们根本不切实际。

本机驱动程序代码还没有脏调度程序访问,但这项工作正在进行中,它可能会在 18.x 版本中作为实验性功能提供。

如果您的 Lua 代码可以利用这些功能中的一项或多项与 Erlang VM 协作,那么您正在尝试的可能是可能的。

【讨论】:

  • 感谢您的深入解释。这让我明白了很多。我将研究这些选项,看看是否能找到解决方案。再次感谢!
猜你喜欢
  • 2012-04-06
  • 2010-10-08
  • 2012-01-10
  • 2017-06-13
  • 2011-10-06
  • 1970-01-01
  • 1970-01-01
  • 2013-10-21
  • 2010-10-27
相关资源
最近更新 更多