avatar nyne
Open App

使用 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 时会将其复制