对象到数字 ID 的映射

zuiainixy2个月前学习心得216

skynet 中使用了一个 hash 表结构来保存服务和 32bit 数字地址的映射关系。

一个 skynet 的服务,其实是一个 C 对象。在有沙盒的系统中,尤其是并行构架,我们很少直接用 C 对象指针来标识一个 C 对象,而是采用数字 id 。用数字做 handle 可以让系统更健壮,更容易校验一个对象是否还有效,还可以减少悬空指针,多次释放对象等潜在问题。比如,操作系统为了把用户程序隔离在用户态,像文件对象就是用数字 id 的形式交给用户程序去用的。

和操作系统通常管理文件句柄的方式不同,skynet 尽量不复用曾经用过的 id 。这样,当你持有了一个已经不存在的 id ,不太会误当作某个新的对象。(操作系统的文件 handle 很容易造成这样的问题:你记住了一个文件,在别的流程中关闭了它,然后又打开了新文件,结果复用了过去的 handle ,导致你操作了错误的文件。很多年前,我就在自己的线上产品中碰到过这样的 bug 。)

但是,一旦尽量不复用 id 。用简单的数组来做映射就很难了。必须使用 hash 表来保证映射效率。在 skynet 中我是这样做的:

每次分配新的 id 时,首先递增上次分配出去的 id (允许回绕,但会跳过 0 。0 被当成无效 id )。然后检查 hash 表中对应的 slot 是否有冲突,若有冲突就继续递增。如果 hash 表的池不够用了,则成倍扩大池,并将内部的数据重新 hash 一次。

这样虽然会浪费一些 id ,但是可以保证 hash 表类的 key 永远不发生碰撞,这样可以让查询速度最快。hash 表的实现也相对简单一些。

我觉得这样的一个数据结构有一定的通用性,今天花了一点时间把 skynet 的这个部分单独抽出来,当成一个独立开源项目重新写了一遍。有兴趣的同学可以在 github 上查看

这里的 struct handlemap 就是这样的一张 hash 表。而 handleid 目前被定义为 unsigned int ,可以在实际使用时定义为其它字长的整数。

一共提供了 5 个 API :

struct handlemap * handlemap_init();
void handlemap_exit(struct handlemap *);

分别是创建和销毁 handlemap 结构,没什么好说的。和 skynet 的实现不同,这里做了一些简化。在销毁 handlemap 时,暂时没有手段去清理里面那些还没有删除的 id 。

我认为在实际使用时,可以通过外部手段去解决。

  • handleid handlemap_new(struct handlemap *, void *ud); 就是为 ud 分配并绑定一个 id 。这个操作一般会成功,但在很少情况下会因为内存不足而失败。失败则返回 0 。 0 在整个系统中永远表示无效 id 。

  • void * handlemap_grab(struct handlemap *, handleid id); 取出 id 对应的对象指针,并在内部增加对它的引用。如果 id 无效,那么会返回 NULL 指针。在实现时,采用了引用计数用来满足线程安全。

  • void * handlemap_release(struct handlemap *, handleid id); 当使用完对象指针后,应该调用这个 api 归还 id 的引用。另外,如果希望删除这个 id 也可以调用它。无论是归还引用还是删除,都必须检查这个 api 的返回值。当返回值不为 NULL 时,表示返回的这个 ud 已经脱离了 handlemap 的管理,通常你需要删除这个对象。


这个数据结构的实现难点在于需要线程安全。在不同的线程中可以并发去 grab 对象,同时也可以安全的删除它们。

我使用了一个读写锁来保证线程安全:

在创建新 id ,或当一个 id release 的时候引用减到零,都会加上写锁,完成成 handlemap 的修改。

而获取(grab)或释放 (release )id 时,则是加的读锁,这些操作是可以并发的。

btw, 和 skynet 不同,这份实现里我支持了 windows 的原子操作 api 。但没有仔细测试,如果有兴趣的同学可以试着用 VC 编译。

skynet 中使用了一个 hash 表结构来保存服务和 32bit 数字地址的映射关系。

一个 skynet 的服务,其实是一个 C 对象。在有沙盒的系统中,尤其是并行构架,我们很少直接用 C 对象指针来标识一个 C 对象,而是采用数字 id 。用数字做 handle 可以让系统更健壮,更容易校验一个对象是否还有效,还可以减少悬空指针,多次释放对象等潜在问题。比如,操作系统为了把用户程序隔离在用户态,像文件对象就是用数字 id 的形式交给用户程序去用的。

和操作系统通常管理文件句柄的方式不同,skynet 尽量不复用曾经用过的 id 。这样,当你持有了一个已经不存在的 id ,不太会误当作某个新的对象。(操作系统的文件 handle 很容易造成这样的问题:你记住了一个文件,在别的流程中关闭了它,然后又打开了新文件,结果复用了过去的 handle ,导致你操作了错误的文件。很多年前,我就在自己的线上产品中碰到过这样的 bug 。)

但是,一旦尽量不复用 id 。用简单的数组来做映射就很难了。必须使用 hash 表来保证映射效率。在 skynet 中我是这样做的:

每次分配新的 id 时,首先递增上次分配出去的 id (允许回绕,但会跳过 0 。0 被当成无效 id )。然后检查 hash 表中对应的 slot 是否有冲突,若有冲突就继续递增。如果 hash 表的池不够用了,则成倍扩大池,并将内部的数据重新 hash 一次。

这样虽然会浪费一些 id ,但是可以保证 hash 表类的 key 永远不发生碰撞,这样可以让查询速度最快。hash 表的实现也相对简单一些。

我觉得这样的一个数据结构有一定的通用性,今天花了一点时间把 skynet 的这个部分单独抽出来,当成一个独立开源项目重新写了一遍。有兴趣的同学可以在 github 上查看

这里的 struct handlemap 就是这样的一张 hash 表。而 handleid 目前被定义为 unsigned int ,可以在实际使用时定义为其它字长的整数。

一共提供了 5 个 API :

struct handlemap * handlemap_init();
void handlemap_exit(struct handlemap *);

分别是创建和销毁 handlemap 结构,没什么好说的。和 skynet 的实现不同,这里做了一些简化。在销毁 handlemap 时,暂时没有手段去清理里面那些还没有删除的 id 。

我认为在实际使用时,可以通过外部手段去解决。

  • handleid handlemap_new(struct handlemap *, void *ud); 就是为 ud 分配并绑定一个 id 。这个操作一般会成功,但在很少情况下会因为内存不足而失败。失败则返回 0 。 0 在整个系统中永远表示无效 id 。

  • void * handlemap_grab(struct handlemap *, handleid id); 取出 id 对应的对象指针,并在内部增加对它的引用。如果 id 无效,那么会返回 NULL 指针。在实现时,采用了引用计数用来满足线程安全。

  • void * handlemap_release(struct handlemap *, handleid id); 当使用完对象指针后,应该调用这个 api 归还 id 的引用。另外,如果希望删除这个 id 也可以调用它。无论是归还引用还是删除,都必须检查这个 api 的返回值。当返回值不为 NULL 时,表示返回的这个 ud 已经脱离了 handlemap 的管理,通常你需要删除这个对象。


这个数据结构的实现难点在于需要线程安全。在不同的线程中可以并发去 grab 对象,同时也可以安全的删除它们。

我使用了一个读写锁来保证线程安全:

在创建新 id ,或当一个 id release 的时候引用减到零,都会加上写锁,完成成 handlemap 的修改。

而获取(grab)或释放 (release )id 时,则是加的读锁,这些操作是可以并发的。

btw, 和 skynet 不同,这份实现里我支持了 windows 的原子操作 api 。但没有仔细测试,如果有兴趣的同学可以试着用 VC 编译。


相关文章

他大专毕业,从负债6万到年入百万,我酸了~~

扎心吗?在持续的通货膨胀下,几十年过去,你拼命工作攒下来的钱有很大一部分都打水漂了。钱真的不是攒出来的。股神巴菲特曾说过:“一生能够积累多少财富,不取决于你能够赚多少钱,而取决于你如何投资理财。”也许...

找不到Android缺少layout_alignParent_Start

我使用的是Android 6.0,API级别23和 我也尝试使用Android 5.1,API级别22 但是在两个设备上都出现了相同的错误I have google the solution and...

自从学会Jenkins自动化,我整个人都升华了

初次使用Jenkins部署自动化,记录下操作过程。安装Jenkins采用安装离线版Jenkins,将文件解压后,在Jenkins文件夹内启动终端,输入命令java -jar jenkins.war -...

ltask :用于 lua 的多任务库

写这个东西的起源是,前段时间我们的平台组面试了一个同学,他最近一个作品叫做 luajit.io 。面试完了后,他专门找我聊了几个小时他的这个项目。他的核心想法是基于 luajit...

欢迎使用Z-BlogPHP!

欢迎使用Z-Blog,这是程序自动生成的文章,您可以删除或是编辑它:)系统生成了一个留言本和一篇《欢迎使用Z-BlogPHP!》,祝您使用愉快!...

强烈推荐款下载不限速解析神器

强烈推荐款下载不限速解析神器

有段时间没有推荐干货给大伙了,今天是时候把压箱底的东西拿出来分享给大家了!小编收集了多款非常好用的工具,可能你已经开始在用了,可能你还不知道它的存在 。如果你已经在用了,说明你很有远光,好用的工具将大...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。