我观察到C NIF在被许多Erlang进程同时调用时的阻塞行为。可以做成非阻塞吗?在这里工作mutex
是否我无法理解?
P.S。一个基本的" Hello world"如果特定sleep
调用它,则可以通过将microseconds
设置为PID
来测试NIF。可以观察到,调用NIF的其他PID等待该睡眠在执行之前执行。
在并发可能不会造成问题的情况下(例如,数组推送,计数器增量),非阻塞行为将是有益的。
我正在分享4个要点的链接,这些要点分别包含spawner
,conc_nif_caller
和niftest
模块。我试图修改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]
答案 0 :(得分:5)
可能会发生一件事(而且我敢打赌这是怎么回事),你是C代码搞砸了Erlang调度程序。
在返回之前执行冗长工作的本机函数会降低VM的响应速度,并可能导致各种奇怪的行为。这种奇怪的行为包括但不限于极端的内存使用,以及调度程序之间的负载均衡。由于工作时间过长而可能发生的奇怪行为也可能因OTP版本而异。
和description表示lengty work
的含义以及如何解决它。
用极少的话说(简化很少):
对于核心,创建了一个调度程序。每个都有一个他可以运行的进程列表。如果一个调度程序列表为空,他将尝试继续使用另一个调度程序列表。如果没有(或者不够)仍然存在,这可能会失败。
Erlang调度程序在一个进程中花费了大量的工作,而不是移动到另一个进程,花费一些工作量,然后转移到另一个进程。等等,等等。这与系统进程中的调度非常相似。
这里非常重要的一件事是计算工作量。默认情况下,每个函数调用都分配了一些减少量。添加可能有两个,模块中的调用函数将有一个,发送消息也是一个,一些内置可能有更多(如list_to_binary
)。如果我们收集2 000个减少量,我们会转移到另一个流程。
那么你的C函数的成本是多少?它只有一次减少。
代码
loop() ->
call_nif_function(),
loop().
可能需要整整一个小时,但调度程序将停留在这一个过程中,因为他仍然没有数到2 000减少。换句话说,他可能被困在NIF内部而无法前进(至少在任何时候)。
around this的方法很少,但一般规则是统计NIF不应该花费很长时间。因此,如果你有很长的C代码,也许你应该使用drivers。他们应该更容易实施和管理,这是对NIF的修补。
答案 1 :(得分:4)
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.
无论如何,还是会产生一些影响。
答案 2 :(得分:4)
我认为关于长期运行的NIF的回复是不合适的,因为你的问题是你正在运行一些简单的“hello world”代码并且只能睡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版本,也会有所帮助。