首页 > 其他分享 ><Erlang> 关于 long-running(长运行) NIF 函数的研究(一)

<Erlang> 关于 long-running(长运行) NIF 函数的研究(一)

时间:2023-03-09 17:46:10浏览次数:36  
标签:heart 127.0 0.1 NIF nif long running loop

  近期对Erlang的NIF函数进行先期的学习和预研,在观看API文档时看到了NIF函数会抢占Erlang 虚拟机调度器线程的问题,导致其它Erlang进程无法正常使用调度器线程,由此阻塞系统及导致各种稀奇古怪的事情发生。

  Erlang官方建议NIF函数返回速度相当快是非常重要的,通常一个行为良好的本地函数会在1毫秒内返回给调用者。如果一个NIF函数需要长时间运行,那么NIF的编写者需要对NIF函数进行切割,即分割成各个子NIF函数,这样能在执行完一个子NIF函数后,VM可以把调度器线程归还给线程池以供给其它进程使用。

  怎么拆分NIF函数,有两个选项:

  1. 从Erlang级别进行一系列NIF调用。
  2. 调用一个NIF,该NIF首先执行一个工作块,然后调用enif_schedule_nif函数来调度另一个NIF调用以执行下一个块。然后,以这种方式调度的最后调用可以返回总体结果。

  选项1Erlang级别调用子NIF比较容易理解,没有进行测试,这里讨论选项2的情况。为了进行测试,smp参数设置-smp +S 1,验证是否能正常返回唯一的调度线程给其它进程使用。测试代码如下(每隔一秒打印一次“loop:n”):

static ERL_NIF_TERM loop_sleep(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]){
    Sleep(1000);
    int n = 0;
    enif_get_int(env, argv[0], &n);
    printf("loop:%d\n",n);
    if(n > 0){
        ERL_NIF_TERM new_argv[] = {enif_make_int(env, n-1)};
        return enif_schedule_nif(env, "loop_sleep", 0, loop_sleep, 1, new_argv);
    }else{
        return atom_ok;
    }
}

static ERL_NIF_TERM test(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]){
    ERL_NIF_TERM new_argv[] = {enif_make_int(env, 10)};
    return enif_schedule_nif(env, "loop_sleep", 0, loop_sleep, 1, new_argv);
}

  Erlang进程代码如下(每隔一秒打印一次“heart:n~~”):

handle_info({heart,N}, State) ->
    case N > 0 of
        true ->
            ?INFO("heart:~w~~~~~n",[N]),
            erlang:send_after(1000, self(), {heart,N-1});
        _ ->
            skip
    end,
    {noreply, State};

  使用nif_server(跑erlang函数)和nif_server2(跑NIF函数)两个进程进行测试,不改变进程的优先级(默认normal),实测控制台打印如下:

点击查看结果
([email protected])12> nif_server:heart().
========= 2023-03-08 12:23:33 [info] nif_server#92 ========
heart:10~~

{heart,10}
([email protected])32>
========= 2023-03-08 12:23:34 [info] nif_server#92 ========
heart:9~~

([email protected])32>
========= 2023-03-08 12:23:35 [info] nif_server#92 ========
heart:8~~

([email protected])32> nif_server2:mfa(nif_random2,test,[{}]).
loop:10
loop:9
loop:8
loop:7
okloop:6
loop:5
loop:4
loop:3
loop:2
loop:1
loop:0


========= 2023-03-08 12:23:41 [info] nif_server#92 ========
heart:7~~

([email protected])33>
========= 2023-03-08 12:23:48 [info] nif_server#92 ========
heart:6~~

  可以看起来貌似并不能正常将调度器线程归还(实际情况并不是),辛苦做的拆分NIF并没有什么用,尝试将
return enif_schedule_nif(env, "loop_sleep2", 0, loop_sleep, 1, new_argv);改成return enif_schedule_nif(env, "loop_sleep", ERL_NIF_DIRTY_JOB_CPU_BOUND, loop_sleep, 1, new_argv):

点击查看结果
([email protected])12> nif_server:heart().
========= 2023-03-08 14:29:37 [info] nif_server#92 ========
heart:10~~

{heart,10}
([email protected])43>
========= 2023-03-08 14:29:38 [info] nif_server#92 ========
heart:9~~

([email protected])43>
========= 2023-03-08 14:29:39 [info] nif_server#92 ========
heart:8~~

([email protected])43> nif_server2:mfa(nif_random2,test,[{}]).
ok
([email protected])44>
========= 2023-03-08 14:29:40 [info] nif_server#92 ========
heart:7~~

([email protected])44> loop:10

========= 2023-03-08 14:29:41 [info] nif_server#92 ========
heart:6~~

([email protected])44> loop:9

========= 2023-03-08 14:29:42 [info] nif_server#92 ========
heart:5~~

([email protected])44> loop:8

  这种情况倒是符合要求,只不过变成了脏调度函数,也就是两个调度器在虚拟机层面平行使用,自然不会互相影响,需要说明的是,虽然没有指定 +SDcpu,但是默认情况下脏调度线程数量同普通调度线程数量是一致的。

erl -name [email protected] -pa ../ebin -s game -smp +S 1
Eshell V9.3  (abort with ^G)
([email protected])1> erlang:system_info(dirty_cpu_schedulers_online).
1

  那么上面的普通调度是怎么回事呢,其实问题是NIF函数所花费的时间片(reductions)是固定的,如果一次调度没有达到指定的reductions,而进程又还有任务可以继续处理,则调度器线程不会让给别的进程使用,这样做的目的是为了避免调度器在进程池里频繁切换进程带来多余的开销。那么解决办法就是,用enif_consume_timeslice重新帮助NIF函数重新估算reductions,道理就是告诉当前NIF函数该退出了,不要继续执行。这种写法想想明显不太适用普通业务型代码。。so,遵照官方的建议,一个行为良好的本地函数会在1毫秒内返回给调用者!!!更长的函数或者无法控制时间的第三方库,尝试使用os线程(也就是异步方式)或者脏调度吧。

标签:heart,127.0,0.1,NIF,nif,long,running,loop
From: https://www.cnblogs.com/karl2013/p/17199362.html

相关文章

  • <Erlang> 关于 long-running(长运行) NIF 函数的研究(二)
    关于脏NIF函数需要知道的一些知识默认情况下,虚拟机给你N个脏CPU调度器,其中N是正常调度器的数量。正常调度器的数量默认为系统上配置的逻辑处理器的数量。不指定脏调度器......
  • <Erlang> 关于 long-running(长运行) NIF 函数的研究(三)
    使用底层操作系统的线程解决长NIF函数问题  Erlang运行时使用普通调度器线程来运行Erlang函数和普通NIF函数,通过脏调度器运行脏NIF函数,除了这两种方式外,你还可以使用eni......
  • 控制台Problems提示:Duplicated code fragment (12 lines long)
      代码段里有黄色的波浪线翻译过来就是"重复的代码片段(12行)",这是pycharm提示你在同一个项目中存在相同的代码片段,也是间接性提醒你优化自己的代码,不想提示黄色的波浪......
  • springboot 2.6版本使用knife4j的坑
    1.yml增加配置application.yml spring: mvc:   pathmatch:     matching-strategy:ANT_PATH_MATCHER  https://blog.csdn.net/weixin_44307818/arti......
  • repo manifest.xml
    repoManifestFormat<?xmlversion="1.0"encoding="UTF-8"?><manifest><remotename="aosp"#在每一个.git/config文件的remote项中用到这个name。remote......
  • EBS APP_CALCULATE.RUNNING_TOTAL的用法
    有时候需要显示某个栏位的汇总数量,且在例如新增、删除、修改记录的时候,汇总项的值要相应地改变,如果直接使用Form中的SUM属性功能,对于清除等操作要进行复杂处理。Oracle提供......
  • 这样在 C# 使用 LongRunnigTask 是错的
    Task.Factory.StartNew有一个重载,是支持TaskCreationOptions.LongRunning参数来指定Task的特征的。但是可能在没有注意的情况下,你就使用了错误的用法。那么本文我们来......
  • [论文速览] LayoutLMv3@ Pre-training for Document AI with Unified Text and Image
    Pretitle:LayoutLMv3:Pre-trainingforDocumentAIwithUnifiedTextandImageMaskingaccepted:ACMMM2022paper:https://arxiv.org/abs/2204.08387code:htt......
  • SpringBoot 项目集成 knife4j
    文档地址:https://doc.xiaominfo.com/knife4j是为JavaMVC框架集成Swagger生成\(Api\)文档的增强解决方案。Swagger介绍前后端分离开发模式中,api文档是最好的沟通......
  • 增强Swagger==Knife4j
    这是官网:https://doc.xiaominfo.com/你只需要引入pom<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi2-spring-boot-starter......