简述

在某个需求的驱动下,希望实现线程间共享的路由树。鉴于如下的核心需求:

  • 读多写少,写入操作重,读取操作轻。
  • 性能足够快,不希望有单点。
    鉴于有数据共享需求,没有单点的情况下,erlang中几乎只有ets了。因为ets仅能实现k,v查询,不能实现更复杂的读写需求,比如寻找某个子网(key="192.168.x.x")下的任意IP(values = ["192.168.1.1", "192.168.2.3"]),于是决定用NIF来实现。这是第一次尝试在实际业务中使用NIF。这里做一下总结。

实现时的考量

数据转换 erlang_term to c and c to erlang_term

大部分语言都提供c拓展,第一件事就是,如何把语言的内置类型转为c中的类型,以及如何将c中的数据结构转为语言的内置类型。
这里可以

  • 直接使用enif_get/enif_make 系列接口。
  • 参考 https://github.com/goertzenat...,利用c++ template,在编译期自动根据类型展开雷同的 enif_get/enif_make 系列代码。

资源管理

参考 https://www.erlang.org/doc/ma...
主要有两种:

  • 自己管理,比如将资源存储在静态变量中,提供 destroy_nif 之类,由用户销毁。
  • erlang管理,nif不管理资源,erlang_term 复制出去后,由erlang的引用计数管理。

动态类型

可以用enif_term_to_binary/enif_binary_to_term系列接口,在同样的数据结构中存储动态的erlang结构。

线程安全

一般来说,nif是在erlang的scheduler线程执行的。所以,需要用enif_mutex/enif_rwlock 系列接口保护资源,避免未定义的行为。也可以直接使用c++的mutex,操作系统api等等。
如:https://en.cppreference.com/w...

调度器和上下文切换

erlang调用nif是没有上下文切换的。那么就会有一个问题,如果nif一直占用cpu怎么办?如何公平调度?
https://www.erlang.org/doc/ma...
nif函数应该尽快(1ms内)返回。否则,会导致一些异常行为,我遇到的情况是日志刷不出来(调度异常)。为避免这种情况,我们需要更复杂的策略如下。

协程化:

  • 用 enif_consume_timeslice 看自己是否还有时间片。
  • 用 enif_schedule_nif yield to scheduler。

    异步化:

    另起thread计算,在用 enif_send 将结果发回去,这样不影响调度线程。

    Dirty Nif:

    其实就是官方的worker thread,按cpu/io bound分类,避免影响普通调度器。

调试

占位。原理上,和普通c程序调试动态库没有太多区别。工作流倒是可以记录一下。

总结

  1. 没有银弹,nif有其使用的场景:小消息,大计算。

参考

  1. https://www.erlang.org/doc/ma...
  2. https://www.erlang.org/doc/tu...
  3. https://github.com/goertzenat...

enjolras1205
77 声望9 粉丝