使用 dart ffi 实现多 Isolate 共享数据
起因
正在写 dart webdav 服务端, 需要记录一个全局的文件加锁状态. 在 dart 中写 web 服务器时通常需要开多个 Isolate 从而使用多线程, 但 Isolate 之间不能共享数据.
解决问题
为了解决这个问题, 可以用 ffi 手动分配内存, 这样分配的内存可以被所有 Isolate 共享.
为了提高效率, 我决定写一个哈希表. 哈希表的键使用 int 类型即可, 因为可以直接使用 dart 对象的hashcode
作为键. 值也使用 int 类型, 表示是否加锁. 可惜不能使用模板, Struct
类型不允许.
节点的定义如下:
final class IntIntMapNode extends ffi.Struct {
@ffi.Uint32()
external int key;
@ffi.Uint32()
external int value;
external ffi.Pointer<IntIntMapNode> next;
external ffi.Pointer<IntIntMapNode> prev;
IntIntMapNode._();
}
关于哈希函数, 因为键是通过hashcode
方法得到的, 因此取个模就可以了
int _hash(int key) => key % _mapSize;
完整实现如下:
class NativeIntIntMap extends MapBase<int, int> {
late ffi.Pointer<ffi.Pointer<IntIntMapNode>> _head;
static const _mapSize = 1024;
NativeIntIntMap() {
_head = calloc(_mapSize);
}
int _hash(int key) => key % _mapSize;
@override
int? operator [](Object? key) {
if (key is! int) {
return null;
}
var index = _hash(key);
var current = _head[index];
while (current != ffi.nullptr) {
if (current.ref.key == key) {
return current.ref.value;
}
current = current.ref.next;
}
return null;
}
@override
void operator []=(key, value) {
var node = calloc<IntIntMapNode>();
node.ref.key = key;
node.ref.value = value;
var index = _hash(key);
var current = _head[index];
if (current == ffi.nullptr) {
_head[index] = node;
} else {
do {
if (current.ref.key == key) {
current.ref.value = value;
return;
}
current = current.ref.next;
}
while (current.ref.next != ffi.nullptr);
current.ref.next = node;
node.ref.prev = current;
}
}
@override
void clear() {
for (var i = 0; i < _mapSize; i++) {
var current = _head[i];
while (current != ffi.nullptr) {
var next = current.ref.next;
calloc.free(current);
current = next;
}
_head[i] = ffi.nullptr;
}
}
@override
Iterable<int> get keys sync* {
for (var i = 0; i < _mapSize; i++) {
var current = _head[i];
while (current != ffi.nullptr) {
yield current.ref.key;
current = current.ref.next;
}
}
}
@override
int? remove(Object? key) {
if (key is! int) {
return null;
}
var index = _hash(key);
var current = _head[index];
while (current != ffi.nullptr) {
if (current.ref.key == key) {
if (current.ref.prev == ffi.nullptr) {
_head[index] = current.ref.next;
} else {
current.ref.prev.ref.next = current.ref.next;
}
if (current.ref.next != ffi.nullptr) {
current.ref.next.ref.prev = current.ref.prev;
}
var value = current.ref.value;
calloc.free(current);
return value;
}
current = current.ref.next;
}
return null;
}
void dispose() {
clear();
calloc.free(_head);
}
}
final class IntIntMapNode extends ffi.Struct {
@ffi.Uint32()
external int key;
@ffi.Uint32()
external int value;
external ffi.Pointer<IntIntMapNode> next;
external ffi.Pointer<IntIntMapNode> prev;
IntIntMapNode._();
}
使用时, 只要在创建handler
时创建这个类就可以了, dart 开 Isolate 时会将其复制