Erlang 生成大量 C 进程
Erlang spawning large amounts of C processes
我一直在研究如何在 Erlang 中嵌入语言(我们以 Lua 为例)。这当然不是一个新想法,有很多图书馆可以做到这一点。但是我想知道是否可以启动状态由 Lua 修改的 Genserver。这意味着一旦启动了 Genserver,它将启动一个 (long 运行ning) Lua 进程来操作 Genserver 的状态。我知道这也是可能的,但我想知道我是否可以生成 1,000 10,000 甚至 100,000 个这些进程。
我不太熟悉这个话题,但我做了一些研究。
(如果我对这些选项有任何错误,请纠正我)。
TLDR;跳到最后一段。
第一个选项:NIFs:
这似乎不是一个选项,因为它会阻止当前进程的 Erlang 调度程序。如果我想生成大量这些,它会冻结整个 运行 时间。
第二个选项:端口驱动程序:
它类似于 NIF,但通过向指定端口发送数据进行通信,也可以将数据发送回 Erlang。这很好,尽管这似乎也阻止了调度程序。我已经尝试过一个库,它也为你做锅炉平台,但它似乎在生成 10 个进程后阻塞了调度程序。我还查看了 Erlang 文档中的 postgresql 示例,该示例据说是异步的,但我无法使示例代码正常工作(R13?)。是否有可能 运行 尽可能多的端口驱动程序进程而不阻塞 运行 时间?
第三个选项:C节点:
我觉得这很有趣,想尝试一下,但显然 "erlang-lua" 项目已经这样做了。这很好,因为如果出现问题并且进程被隔离,它不会使您的 Erlang VM 崩溃。但是为了实际生成单个进程,您需要生成整个节点。我不知道这有多贵。我也不确定集群中连接节点的限制是多少,但我没有看到自己生成 100,000 个 C 节点。
第四个选项:端口:
起初我以为这与端口驱动程序相同,但实际上是不同的。您生成一个执行应用程序并通过 STDIN 和 STDOUT 进行通信的进程。这对于生成大量进程非常有效,而且(我认为?)它们不会对 Erlang VM 构成威胁。但是,如果我要通过 STDIN / STDOUT 进行通信,为什么还要费心使用可嵌入的语言呢?也可以使用任何其他脚本语言。
因此,在对一个我不熟悉的领域进行了大量研究之后,我来到了这个领域。您可以将 Genserver 作为 "entity",其中 AI 是用 Lua 编写的。这就是为什么我希望每个实体都有一个流程。我的问题是如何实现产生许多与长 运行ning Lua 进程通信的 Genservers?这可能吗?我应该以不同的方式处理我的问题吗?
如果你能让 Lua 代码——或者更准确地说,它的底层本地代码——与 Erlang VM 合作,你有几个选择。
考虑 Erlang VM 最重要的功能之一:跨相对较小的调度程序线程集管理(可能大量)Erlang 轻量级进程的执行。它使用多种技术来了解进程何时用完其时间片或正在等待,因此应该安排给另一个进程一个机会 运行.
您似乎在问如何将本机代码获取到 运行,但它喜欢在 VM 中使用,但正如您已经暗示的那样,本机代码可能导致 VM 出现问题的原因是它没有实用的方法来阻止本机代码完全接管调度程序线程,从而阻止常规 Erlang 进程的执行。因此,本机代码必须协作将调度程序线程返回给 VM。
对于较旧的 NIF,此类合作的选择是:
- 将 NIF 在调度程序线程上调用 运行 的时间保持在 1 毫秒或更短。
- 创建一个或多个私有线程。 T运行将每个长运行ning NIF 调用从其调度程序线程转移到私有线程执行,然后return 调度程序线程到VM。
这里的问题是并非所有调用都可以在 1 毫秒或更短时间内完成,而且管理私有线程可能容易出错。为了解决第一个问题,一些开发人员将工作分解为多个块,并使用 Erlang 函数作为包装器来管理一系列短的 NIF 调用,每个调用完成一个工作块。至于第二个问题,好吧,有时候你就是无法避免它,尽管它本身就很困难。
NIFs 运行 Erlang 17.3 或更高版本上的 ning 也可以使用 enif_schedule_nif
function. To use this feature, the native code has to be able to do its work in chunks such that each chunk can complete within the usual 1ms NIF execution window, similar to the approach mentioned earlier but without the need to artificially return to an Erlang wrapper. My bitwise example code 协作产生调度程序线程,提供了有关此的许多详细信息。
Erlang 17 还带来了一项实验性功能,默认关闭,称为 脏调度程序。这是一组 VM 调度器,它们没有与常规调度器相同的本机代码执行时间限制;那里的工作可以无限期阻塞而不会中断正常的 VM 操作。
脏调度程序有两种形式:CPU 用于 CPU 绑定工作的调度程序,以及 I/O 用于 I/O 绑定工作的调度程序。在编译为启用脏调度器的 VM 中,默认情况下脏 CPU 调度器与常规调度器一样多,并且有 10 I/O 个调度器。可以使用命令行开关更改这些数字,但请注意,要尽量防止常规调度程序饥饿,您永远不能拥有比常规调度程序更多的脏 CPU 调度程序。应用程序使用前面提到的相同 enif_schedule_nif
函数在脏调度程序上执行 NIF。我的 bitwise example code 也提供了很多关于这方面的细节。脏调度程序也将保留为 Erlang 18 的实验性功能。
链接输入端口驱动程序中的本机代码受与 NIF 相同的调度程序执行时间限制,但驱动程序有两个 NIF 没有的功能:
- 驱动程序代码可以将文件描述符注册到 VM 轮询子系统中,并在任何这些文件描述符变为 I/O-ready 时收到通知。
- 驱动程序API支持访问非调度程序异步线程池,其大小是可配置的,但默认情况下有 10 个线程。
第一个特性允许本机驱动程序代码避免阻塞 I/O 的线程。例如,驱动程序代码可以注册套接字文件描述符,而不是执行阻塞 recv
调用,以便 VM 可以轮询它并在文件描述符变得可读时回调驱动程序。
第二个功能提供了一个单独的线程池,可用于不符合调度程序线程本机代码执行时间限制的驱动程序任务。您可以在 NIF 中实现相同的功能,但您必须设置自己的线程池并编写自己的本机代码来管理和访问它。但无论您使用驱动程序异步线程池、您自己的 NIF 线程池还是脏调度程序,请注意它们都是常规操作系统线程,因此尝试启动大量线程根本不切实际。
本机驱动程序代码还没有脏调度程序访问权限,但这项工作正在进行中,它可能会在 18.x 版本中作为实验性功能提供。
如果您的 Lua 代码可以利用这些功能中的一个或多个来与 Erlang VM 协作,那么您的尝试可能是可行的。
我一直在研究如何在 Erlang 中嵌入语言(我们以 Lua 为例)。这当然不是一个新想法,有很多图书馆可以做到这一点。但是我想知道是否可以启动状态由 Lua 修改的 Genserver。这意味着一旦启动了 Genserver,它将启动一个 (long 运行ning) Lua 进程来操作 Genserver 的状态。我知道这也是可能的,但我想知道我是否可以生成 1,000 10,000 甚至 100,000 个这些进程。
我不太熟悉这个话题,但我做了一些研究。 (如果我对这些选项有任何错误,请纠正我)。
TLDR;跳到最后一段。
第一个选项:NIFs:
这似乎不是一个选项,因为它会阻止当前进程的 Erlang 调度程序。如果我想生成大量这些,它会冻结整个 运行 时间。
第二个选项:端口驱动程序:
它类似于 NIF,但通过向指定端口发送数据进行通信,也可以将数据发送回 Erlang。这很好,尽管这似乎也阻止了调度程序。我已经尝试过一个库,它也为你做锅炉平台,但它似乎在生成 10 个进程后阻塞了调度程序。我还查看了 Erlang 文档中的 postgresql 示例,该示例据说是异步的,但我无法使示例代码正常工作(R13?)。是否有可能 运行 尽可能多的端口驱动程序进程而不阻塞 运行 时间?
第三个选项:C节点:
我觉得这很有趣,想尝试一下,但显然 "erlang-lua" 项目已经这样做了。这很好,因为如果出现问题并且进程被隔离,它不会使您的 Erlang VM 崩溃。但是为了实际生成单个进程,您需要生成整个节点。我不知道这有多贵。我也不确定集群中连接节点的限制是多少,但我没有看到自己生成 100,000 个 C 节点。
第四个选项:端口:
起初我以为这与端口驱动程序相同,但实际上是不同的。您生成一个执行应用程序并通过 STDIN 和 STDOUT 进行通信的进程。这对于生成大量进程非常有效,而且(我认为?)它们不会对 Erlang VM 构成威胁。但是,如果我要通过 STDIN / STDOUT 进行通信,为什么还要费心使用可嵌入的语言呢?也可以使用任何其他脚本语言。
因此,在对一个我不熟悉的领域进行了大量研究之后,我来到了这个领域。您可以将 Genserver 作为 "entity",其中 AI 是用 Lua 编写的。这就是为什么我希望每个实体都有一个流程。我的问题是如何实现产生许多与长 运行ning Lua 进程通信的 Genservers?这可能吗?我应该以不同的方式处理我的问题吗?
如果你能让 Lua 代码——或者更准确地说,它的底层本地代码——与 Erlang VM 合作,你有几个选择。
考虑 Erlang VM 最重要的功能之一:跨相对较小的调度程序线程集管理(可能大量)Erlang 轻量级进程的执行。它使用多种技术来了解进程何时用完其时间片或正在等待,因此应该安排给另一个进程一个机会 运行.
您似乎在问如何将本机代码获取到 运行,但它喜欢在 VM 中使用,但正如您已经暗示的那样,本机代码可能导致 VM 出现问题的原因是它没有实用的方法来阻止本机代码完全接管调度程序线程,从而阻止常规 Erlang 进程的执行。因此,本机代码必须协作将调度程序线程返回给 VM。
对于较旧的 NIF,此类合作的选择是:
- 将 NIF 在调度程序线程上调用 运行 的时间保持在 1 毫秒或更短。
- 创建一个或多个私有线程。 T运行将每个长运行ning NIF 调用从其调度程序线程转移到私有线程执行,然后return 调度程序线程到VM。
这里的问题是并非所有调用都可以在 1 毫秒或更短时间内完成,而且管理私有线程可能容易出错。为了解决第一个问题,一些开发人员将工作分解为多个块,并使用 Erlang 函数作为包装器来管理一系列短的 NIF 调用,每个调用完成一个工作块。至于第二个问题,好吧,有时候你就是无法避免它,尽管它本身就很困难。
NIFs 运行 Erlang 17.3 或更高版本上的 ning 也可以使用 enif_schedule_nif
function. To use this feature, the native code has to be able to do its work in chunks such that each chunk can complete within the usual 1ms NIF execution window, similar to the approach mentioned earlier but without the need to artificially return to an Erlang wrapper. My bitwise example code 协作产生调度程序线程,提供了有关此的许多详细信息。
Erlang 17 还带来了一项实验性功能,默认关闭,称为 脏调度程序。这是一组 VM 调度器,它们没有与常规调度器相同的本机代码执行时间限制;那里的工作可以无限期阻塞而不会中断正常的 VM 操作。
脏调度程序有两种形式:CPU 用于 CPU 绑定工作的调度程序,以及 I/O 用于 I/O 绑定工作的调度程序。在编译为启用脏调度器的 VM 中,默认情况下脏 CPU 调度器与常规调度器一样多,并且有 10 I/O 个调度器。可以使用命令行开关更改这些数字,但请注意,要尽量防止常规调度程序饥饿,您永远不能拥有比常规调度程序更多的脏 CPU 调度程序。应用程序使用前面提到的相同 enif_schedule_nif
函数在脏调度程序上执行 NIF。我的 bitwise example code 也提供了很多关于这方面的细节。脏调度程序也将保留为 Erlang 18 的实验性功能。
链接输入端口驱动程序中的本机代码受与 NIF 相同的调度程序执行时间限制,但驱动程序有两个 NIF 没有的功能:
- 驱动程序代码可以将文件描述符注册到 VM 轮询子系统中,并在任何这些文件描述符变为 I/O-ready 时收到通知。
- 驱动程序API支持访问非调度程序异步线程池,其大小是可配置的,但默认情况下有 10 个线程。
第一个特性允许本机驱动程序代码避免阻塞 I/O 的线程。例如,驱动程序代码可以注册套接字文件描述符,而不是执行阻塞 recv
调用,以便 VM 可以轮询它并在文件描述符变得可读时回调驱动程序。
第二个功能提供了一个单独的线程池,可用于不符合调度程序线程本机代码执行时间限制的驱动程序任务。您可以在 NIF 中实现相同的功能,但您必须设置自己的线程池并编写自己的本机代码来管理和访问它。但无论您使用驱动程序异步线程池、您自己的 NIF 线程池还是脏调度程序,请注意它们都是常规操作系统线程,因此尝试启动大量线程根本不切实际。
本机驱动程序代码还没有脏调度程序访问权限,但这项工作正在进行中,它可能会在 18.x 版本中作为实验性功能提供。
如果您的 Lua 代码可以利用这些功能中的一个或多个来与 Erlang VM 协作,那么您的尝试可能是可行的。