Erlang:阻止C NIF调用行为



当许多Erlang进程同时调用C NIF时,我观察到它们的阻塞行为。它可以不阻塞吗?这里是不是有一个mutex在工作,我无法理解?

p.S.一个基本的"Hello world"NIF可以通过将其设为100个microsecondssleep来测试,以防特定的PID调用它。可以观察到,调用NIF的其他PID在执行之前等待该睡眠执行。

在并发可能不会造成问题的情况下(例如,数组推送、计数器增量),非阻塞行为将是有益的。

我正在共享到4个寄存器的链接,这些寄存器分别由spawnerconc_nif_callerniftest模块组成。我试着修改Val的值,我确实观察到了一种非阻塞行为。这可以通过向spawn_multiple_nif_callers函数分配一个大整数参数来确认。

链接spawner.erl,conc_nif_caller.erl、niftest.erl,最后是niftest.c

下面的行是由我的Mac上的Erlang REPL打印的。

Erlang/OTP 17 [erts-6.0] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

NIF本身没有任何互斥。您可以在C中实现一个,当您加载NIF的对象时也有一个,但这应该只在加载模块中完成一次。

可能会发生的一件事(我敢打赌这就是发生的事情)是,您的C代码扰乱了Erlang调度程序。

本机函数在返回之前做了很长的工作,这将降低VM的响应能力,并可能导致各种奇怪的行为。这种奇怪的行为包括但不限于极端的内存使用,以及调度器之间不好的负载平衡。由于长时间工作而可能发生的奇怪行为也可能因OTP版本而异。

以及描述lengty work的含义以及如何解决它。

用很少的话(几乎没有简化):

为核心创建一个调度程序。每个人都有一个他可以运行的进程列表。如果一个调度程序列表为空,他将尝试仍然从另一个列表工作。如果没有任何东西(或没有足够的东西)可以静止,这可能会失败。

Erlang调度程序在一个进程中花费一定的工作量,而不是转移到另一个进程,在那里花费一定的工作,然后转移到另外一个进程。等等,等等。这与系统进程中的调度非常相似。

这里非常重要的一件事是计算工作量。默认情况下,每个函数调用都分配了一定数量的减少。添加可能有两个,模块中的调用函数将有一个,发送消息也有一个。一些内置可能有更多(如list_to_binary)。如果我们收集了2000个削减,我们将进入另一个过程。

那么,你的C函数的成本是多少呢?这只是一个减少。

类似的代码

loop() ->
   call_nif_function(),
   loop().

可能需要整整一个小时,但调度人员将被困在这一过程中,因为他还没有计算到2000个减少。或者换句话说,他可能会被困在NIF内部,没有前进的可能(至少在短期内)。

有几种方法可以解决这个问题,但一般规则是统计NIF不应该花费很长时间。所以,如果你有长时间运行的C代码,也许你应该使用驱动程序。它们应该更容易实施和管理,而不是修补NIF。

我认为关于长时间运行的NIF的回答是错误的,因为你的问题说你正在运行一些简单的"你好世界"代码,并且只睡了100个小时。的确,理想情况下,NIF调用不应该超过一毫秒,但是你的NIF可能不会导致调度程序问题,除非它们一次持续运行几十毫秒或更长时间。

我有一个名为rev/1的简单NIF,它接受一个字符串参数,反转它,然后返回反转的字符串。我在其中插入了一个usleep调用,然后派生了100个并发的Erlang进程来调用它。下面显示的两个线程堆栈基于Erlang/OTP 17.3.2,同时显示了rev/1 NIF中的两个Erlang调度程序线程,一个在我在NIF C函数本身上设置的断点处,另一个在NIF:中的usleep上被阻塞

Thread 18 (process 26016):
#0  rev (env=0x1050d0a50, argc=1, argv=0x102ecc340) at nt2.c:9
#1  0x000000010020f13d in process_main () at beam/beam_emu.c:3525
#2  0x00000001000d5b2f in sched_thread_func (vesdp=0x102829040) at beam/erl_process.c:7719
#3  a0x0000000100301e94 in thr_wrapper (vtwd=0x7fff5fbff068) at pthread/ethread.c:106
#4  0x00007fff8a106899 in _pthread_body ()
#5  0x00007fff8a10672a in _pthread_start ()
#6  0x00007fff8a10afc9 in thread_start ()
Thread 17 (process 26016):
#0  0x00007fff8a0fda3a in __semwait_signal ()
#1  0x00007fff8d205dc0 in nanosleep ()
#2  0x00007fff8d205cb2 in usleep ()
#3  0x000000010062ee65 in rev (env=0x104fcba50, argc=1, argv=0x102ec8280) at nt2.c:21
#4  0x000000010020f13d in process_main () at beam/beam_emu.c:3525
#5  0x00000001000d5b2f in sched_thread_func (vesdp=0x10281ed80) at beam/erl_process.c:7719
#6  0x0000000100301e94 in thr_wrapper (vtwd=0x7fff5fbff068) at pthread/ethread.c:106
#7  0x00007fff8a106899 in _pthread_body ()
#8  0x00007fff8a10672a in _pthread_start ()
#9  0x00007fff8a10afc9 in thread_start ()

如果Erlang模拟器中有任何互斥体阻止并发NIF访问,那么堆栈争用将不会显示C NIF中的两个线程。

如果你发布你的代码,这样那些愿意帮助解决这个问题的人就可以看到你在做什么,也许可以帮助你找到任何瓶颈。如果您告诉我们您使用的Erlang/OTP的版本,也会很有帮助。

NIF调用会阻塞调用它们的进程绑定到的调度器。因此,对于您的示例,如果其他进程在同一个调度程序上,则在第一个进程完成之前,它们不能调用NIF。

在这方面,您不能使NIF呼叫不阻塞。然而,您可以生成自己的线程,并将工作的主要部分卸载给它们。

这样的线程可以向本地Erlang进程(同一台机器上的进程)发送消息,因此,您仍然可以通过等待派生的线程发回消息来获得所需的响应。

一个糟糕的例子:

static ERL_NIF_TERM my_function(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
    MyStruct* args = new MyStruct(); // I like C++; so sue me
    args->caller = enif_self();
    ErlNifTid thread_id;
    // Please remember, you must at some point rejoin the thread, 
    // so keep track of the thread_id
    enif_thread_create("my_function_thread", &thread_id, my_worker_function, (void*)args, NULL);
    return enif_make_atom(env, "ok");
}
void* my_worker_function(void* args) {
    sleep(100);
    ErlNifEnv* msg_env = enif_alloc_env();
    ERL_NIF_TERM msg = enif_make_atom(msg_env, "ok");
    enif_send(NULL, args->caller, msg_env, msg);
    delete args;
    return NULL;
}

在你的erlang来源:

test_nif() -> 
    my_nif:my_function(),
    receive
        ok -> ok
    end.

不管怎么说,有这样的效果。

相关内容

  • 没有找到相关文章

最新更新