简述
在某个需求的驱动下,希望实现线程间共享的路由树。鉴于如下的核心需求:
- 读多写少,写入操作重,读取操作轻。
- 性能足够快,不希望有单点。
鉴于有数据共享需求,没有单点的情况下,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程序调试动态库没有太多区别。工作流倒是可以记录一下。
总结
- 没有银弹,nif有其使用的场景:小消息,大计算。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。