可扩充类型handle
扩充类型object
, program
, domain
,coroutine
,timer
,socket
,share_value
,sync_object
, ref_value
等类型都是扩充自handle,
如果需要扩充自己的类型,则应继承自handle
handle
handle翻译为“句柄”,也就是“把手”的意思,可以理解为用来方便控制某个东西的东西。
Handle用于管理某个Resource
- 通过引用计数确保Resource指针有效
- 通过Handle上的Lock确保Resource相关操作的线程安全
- HandlePtr 内部用一个64bit的整形表示,包含类型,索引分页,索引偏移,版本 信息
- 创建handle的时候,引用计数初始值为1
Handle的组织结构
字段 | 大小 | 描述 |
---|---|---|
m_state | 1Byte | Handle的状态,有UNUSED,OPENED,CLOSED,这个只是Handle状态,Resource自己应该也有状态 |
m_ref | 4Byte | Handle的引用计数,创建时初始值为1 |
m_handle_id | 8Byte | Handle对应的ID,64位非负整形,包含Handle的类型/索引/版本信息 |
m_resource | 8Byte | Handle对应的资源指针 |
m_lock | 不定 | 不同的Lock实现大小可能不同, 用来实现资源的锁定和解锁,确保共享数据线程安全 |
m_entry | 不定 | 额外的自定义信息,如Object就会额外存放domain,看确定是否在当前域,这样可以免去引用计数和锁 |
Handle状态说明
- handle有四个内置状态:INVALID, INITIALIZING, USING, CLOSED
- INVALID为初始状态,此时的handle还没被分配,不可使用
- 创建一个新的句柄后,句柄就处于INITIALIZING状态,句柄初始化中,ref必定大于0,此时不能被close
- 对句柄进行Open操作后,句柄就处于USING状态,句柄可以被正常使用,ref必定大于0,此时可以被close
- 关闭句柄后(调用句柄的close)句柄处于CLOSED状态,此后所有cast_opened_handle将失败,当ref变为0时,清除并回收句柄
Handle生命周期
- 分配阶段 new出一个资源后,通过allocate_handle分配一个资源对应的handle
- 初始化段 得到handle后(此时不需要通过dummy锁住),进行初始化操作
- 使用阶段 通过handle的open方法,将handle和某个资源绑定,这个open是指handle的open并不一定是资源真正的open,资源本身如果需要单独的打开流程需要自己处理(一般应在此open操作之前完成),自身的状态也需要自己处理,不应该依赖handle的状态
- 关闭阶段 通过handle的close方法,主动减少一次引用计数,并 将状态设为CLOSED,close必须在lock范围内调用,此后所有cast_opened_handle操作将会失败。close时会调用close_resource函数。等到handle的引用计数减为0,则调用资源的free_resource函数,并回收句柄。close方法仅能在USING状态下使用 注意:资源的free_resource函数中不能有lock/unlock操作,否则引用计数改变会导致再次关闭
Handle操作流程
- 当我们需要在多线程环境下对某个资源进行操作的时候,需要使用Handle来保证线程安全
- 先通过Handle::cast_opened_handle(handle_id)在栈上获取Handle的dummy对象
- 刚获取的时候,只是引用计数+1了,而并未进行lock
- 我们可以对资源进行lock和unlock操作来确保某段读写资源的代码不会重入
- 在中间过程中产生的任何返回或异常会导致栈回溯,dummy对象被自动销毁,自动进行unref和unlock
- 如果需要在退出函数前就把引用计数归还,可以手动调用dummy的unref减引用计数,此后dummy将失效
- 正常情况下,应该让dummy自动销毁,在析构中unref
- 资源的打开要自己实现一些逻辑,涉及到ref的加减,要格外小心
- 资源关闭可以使用通用的关闭方法Handle::close_handle_resource,如果特殊需求也可以自己实现
- Handle::take_raw_handle(id) 可以获取到Handle对象的引用,一般情况不建议使用,一旦过程中返回或抛异常,可能会导致没有正确unlock或者unref