对象生命周期管理和垃圾回收机制

对象生命周期管理和垃圾回收机制:引擎使用引用计数来管理对象的生命周期。但是引用计数无法处理循环引用的问题,这时就需要使用标记清理方法来进行垃圾回收。

垃圾回收方案:定义全局 gcroot,存储所有的 root object,定义GCPtr 封装类,和 smartptr 类似,在增加、减少引用计数时根据对象是否被其他对象引用而采用不同的回收方案:

  • 没有对象引用,在增加引用计数时 attach gcroot 中,在减少计数时从 gcroot detach,在 gc 函数里使用标准回收方法,先标记、再回收,即使对象的引用计数不为0也回收,这就解决了引用计数无法处理的循环引用的问题。
  • 有对象引用使用引用计数来控制对象生命周期。

 

判断对象是否被其他对象引用:需要记录对象之间引用关系,也就是形成对象关系图,每个对象可以有多个引用者,在 GCObject中提供设置关系的接口(AttachTo\Detach)。设置对象关系可以有多种渠道,一种是通过对象系统的反射机制来设置,通过 SetProperty 等方法,(组件对象无需使用垃圾回收?),一种是手工调用关系设置接口。

优化:扫描的过程还是很耗费时间,同时有些对象一直存在于系统之中,生命周期很长,垃圾回收时就不需要处理这些对象。当使用 GCPtr 来访问对象时,对象处于垃圾回收的机制下,当直接使用对象原生指针时,垃圾回收不会处理这个对象。创建对象时也可以指定对象标志,这样即使使用 GCPtr 访问对象,对象也不会被垃圾回收,但是可能存在循环引用问题。

另一个问题是扫描过程会遍历整个系统中所有对象,这会导致频繁的内存页交换,可以考虑将对象标记数据、对象关系数据以及 gc root 数据放置在一个单独连续的内存区域,这样就避免了访问对象造成的页交换,提高GC 性能。

Advertisements
发表在 Uncategorized | 留下评论

Hello world!

Welcome to WordPress.com. This is your first post. Edit or delete it and start blogging!

发表在 Uncategorized | 一条评论

关于 D3D 的资源管理的那点事

        引擎中的异步资源管理,其中重要部分是涉及到 D3D 设备资源的管理。在 CreateX Engine 中,大部分 D3D 资源,除了必须以 Default 方式创建的之外,基本都是 Managed Pool 管理的。以 Texture 为例,当申请一个纹理资源时,除了创建引擎逻辑上的纹理对象外,最终还要创建 D3D 的设备对象,很显然,在不开启 D3D 设备多线程安全的模式下,设备资源的创建必然是在渲染线程中完成的,若要不希望渲染线程被阻塞,关键在于填充数据这一步。我采取的做法是:1.在渲染线程 Lock,将 Lock 返回指针和要填充的内存数据投递给资源上传线程;2.资源上传线程 memcpy,通知渲染线程;3.渲染线程 Unlock。我曾经把这个方案介绍给腾讯的同事,有人提出疑问:即使将 copy 数据的过程放在了另一个线程,但是调用在 Unlock API 时 Driver 还是要把数据再 upload 到显存中,这个过程是在渲染线程中完成的,所以本质上还是阻塞了渲染线程。对此我的回答是这样的:Managed 的方式创建的资源,在 Unlock 之后并不是立刻 Upload 到显存中,众所周知,Default 资源是常驻本地显存(On-chip Memory)中的,当一个 Default 资源被创建时,立即在本地显存中分配出一段内存留给这个资源。而 Managed  资源在创建时并不立即分配显存,而是在非本地显存(AGP 内存)中保存一份相同的数据,当渲染需要用到这个资源时,Driver 会判断资源是否在本地显存中,如果不在才会从非本地显存中 Upload 到本地显存中。用一个 D3D API 也可以佐证我的观点:IDirect3DDevice9::EvictManagedResources,这个函数的作用是清除本地显存中的所有 Managed 资源,经常做为当显存不足无法创建 D3D 资源对象时的处理手段。如果这一事实成立,上述异步创建 D3D 资源的流程就是有效的。但不幸的是这个观点无法得到官方文档的证实,在 D3D SDK 文档中没有任何说明 Unlock 后发生了什么,也许只有 GPU 厂商知道。直到今天 ATI 的技术人员来公司做技术演示,在交流过程中总算是对之前的这个疑惑有了官方的解答:

        对于 Managed 资源,在 Unlock 后并不是立即提交到本地显存,而是延迟提交,也就是渲染时用到即时提交;对于 Default 资源,有2种情况:1.Lock 返回的是本地 Visible 显存地址。这里解释一下,ATI GPU Driver 内部会把本地显存分为两种:一种是 Visible,一种是 Invisible,这里所谓的 visible 是指对 CPU 而言,也就是说 Visible 的本地显存地址可以映射到 CPU 可见得的系统地址空间中,这样当 Lock 时,根据资源创建的标志和 Lock 标志,返回的有可能是 Visible 的本地显存的映射地址,这时向 Lock 返回的地址 copy 数据实际上就是向本地显存直接 upload 数据,也就同时发生了总线数据传输。2.Lock 返回的是非本地显存的地址,由于非本地显存对 CPU 也是可见的(可映射的),这时的 copy 操作实际上是将数据 copy 到临时的非本地显存中,当 Unlock 时就是把非本地显存的临时数据再 copy 到本地显存中。

        当我把我的多线程 D3D 设备资源管理方案和 ATI 技术人员介绍了之后,他认为这样做没有问题,确实可以提高加载性能,而不阻塞渲染线程。当然,这还只是 ATI 的驱动的实现方式,我计划下次跟 Nvidia 技术交流时再提相同问题,看看 Nvidia Driver 又是如何处理的。不过我相信大同小异。

发表在 Uncategorized | 留下评论

GPU 加速的静态 AO 烘焙

说明:这是我在公司的项目中实现的技术,日前写了篇用于培训讲解的 PPT,去掉了有关于地球安全机密的东西就放上来了。

GPU 加速的静态 AO 烘焙

简介

Ambient Occlusion (AO)

AO 是全局光照(GI)技术中重要的组成部分,AO 描述的是物体表面上点和场景中其他物体的遮挡值,在全局光照中使用 AO 来衰减光照到达表面的光照值。

计算 AO 的方法

以表面法线为中心向半球(或全球)方向发射 n 条射线,判断每一根射线是否相交,并计算表面点到相交点的距离作为 AO 权重,累加每条射线的 AO 值,作为当前表面点 AO 贡献值。将每个表面的 AO 数据存储在纹理中,我们称之为 AO Map。

XX的光照方案

XX采用静态光照和动态光照结合方式来渲染图形。在XX中,我们希望静态场景能体现 GI 的遮挡效果,所以需要在渲染场景时采样 AO 数据,在光照计算完成之后,采样 AO Map 乘以光照值得到最终效果。

静态 AO 生成的尝试之路

一开始我们采用的是 Beast 来烘焙 AO,但烘焙的性能和效果并不理想。最后我们采用 GPU 的方式来烘焙 AO 纹理,性能和效果都达到预期。

静态 AO 的生成

• Beast 全局光照解决方案

– 离线烘焙全局光照的解决方案

• 提供完整的 SDK

• 易于集成到游戏自己的内容创建工具中

• 多核烘焙支持

• 支持各种光照方式(Point\Dir\Spot\Windows\Area\Sky\Emmisive)

• 灵活的烘焙设定,单独设置烘焙内容,GI\AO\Shadow等

• 问题:烘焙太慢,一个XX的典型场景需要二十个小时。在XX上百个关卡的规模下难以满足要求。而且 Beast 目前还不支持分布式的 AO 烘焙。

GPU 加速的静态 AO 烘焙

• 我们采用 GPU 来加速 AO 的烘焙

• 工作原理:

– 模型上每个点发出的射线方向我们可以认为是相同的,这样以射线方向为视角,可以看做每个方向实际上就是以平行投影的方式渲染一次场景的深度,通过深度判定每个模型上每个点的是否被遮挡,得到当前方向模型表面的遮挡结果,依次循环每个方向,将每次计算的遮挡结果叠加起来,就形成最终的 AO 数据。

GPU 加速的静态 AO 烘焙实现

• 核心流程

clip_image002[1]

实现细节

• 生成球形的射线

– 这是非常重要的步骤,由于要求生成的射线是均匀分布在球面上,否则烘焙的效果很差。所以我们采用的是球谐光照中生成均匀分布的球形射线的方法。根据测试发现,900根射线是性价比较合适的值。

• 渲染深度图

– 因为我们假设物体所有表面上的点发出的 AO 射线方向是同一方向,所以这个步骤必须将投影矩阵设置为平行投影方式,可以根据渲染物体的包围盒生成投影矩阵。

• 计算 AO 值

– 得到深度以后,采用和 Shadow Mapping 一样机制计算物体表面的 AO 信息。这一步需要注意的是生成的方式,将需要生成 AO 的模型渲染到指定大小的 AO Map 的 RT 上,由于 AO Map 保存的是物体上每一个点的 AO 值,所以 AO Map 纹素对应物体上每个点。可以利用 Light Map UV 坐标不重复的特性来生成对应关系,在 VS 阶段将 Light Map UV 作为变换后的顶点位置输出给渲染管线,在 PS 中使用 VS 传入的世界空间位置计算 Shadow,就做到了和模型顶点一一对应了。

• AO 修正

– 和 Light Map 同样存在的问题就是纹理像素的溢出,这需要扩充有效像素缓解这个问题,将得到的 AO 纹理直接渲染到相同大小的 RT 中,在 PS 中对判断当前像素是否无效,如果无效则采样周围 8 个像素,统计并累加其中的有效像素,得到有效像素的平均值,作为当前像素的颜色值,并设置当前像素为有效。如此迭代每个纹素,这样就对 AO Map扩展了1个有效像素,如果不够还可以再做几次,直到结果理想为止。

效果图

clip_image004[1]clip_image006[1]clip_image008[1]

clip_image010[1]clip_image012[1]clip_image014[1]

性能对比

• 测试场景烘焙对比

– Beast: 45分钟

– GPU:2分钟

未来的改进

• 性能:

– 每个物件都要渲染一遍场景深度,对相邻的物体来说,由于位置很接近,深度信息其实可以共享,所以可以考虑一次处理多个相邻的物体,以提高烘焙的速度。

• 质量:

– 可以对生成的结果在进行高斯模糊过滤,产生更加平滑的 AO 效果。

参考资料

• Spherical Harmonic Lighting:The Gritty Details

发表在 Uncategorized | 一条评论

Tessellation of D3D11

去年参加 GDCC 的时候听过 ATI 关于这个技术的专题,当时感觉就是用来做地形真的是很方便:可以实现很高精度的地貌,LOD 是原生支持的,基于这个基础之上实现地形系统代码将大大简化,还有模型的 LOD 也是如此,对美术来说算是轻松了。但是这个技术应用在实际游戏开发中有个主要问题,硬件的普及是一方面,还有就是美术如何生成基于这种技术的资源,也就是资源生产管线。目前次世代标志之一是 Normal Mapping,基于此基础的资源生成管线现在也算成熟,ZBrush->Max\Maya->Engine Editor 这样的流程也基本稳定,美术也得心应手。而 Tessellation 看起来有取代 Normal Mapping 的趋势,因为可以在 GPU 管线中实时生成接近于高模(百万~千万)精度级别的三角形和顶点数据,这样精度的画面远非传统的基于纹理产生视觉假想的 Normal Mapping 所能比拟的,更何况还可以在 HS 和 DS 中传入纹理,实现 Displacement 等多种顶点偏移,甚至是动态变化的效果,反正是只要能想得到,可以搞出很多花样来。据官方称,可以在 64 米的范围内最小细化到 2 毫米的精度,对一般物体来说也足够了。但问题是美术如何制作这样的资源,虽然 Max\Maya 等 CCD 工具早已提供曲面创建功能,但是对于大多数美术来说还是比较陌生,这势必存在一个熟悉过程,正如从上世代过渡到 Normal Mapping 的过程。另外,虽然硬件和软件提供了生成高精度模型的能力,但是能否很好的达到美术期望的效果还有待于实践验证,在 Normal Mapping 时代,美术通过制作高模来烘焙出贴图,制作的过程是美术完全控制的,但基于曲面的建模技术,在不能实时预览的条件下,美术是否还能把握和控制住最终效果还有待于检验,除非 CCD 提供这样的预览能力。关于这两个问题,在去年的 GDCC 的那个会议上我问起过,当时 ATI 的主讲人回答的也比较模糊,但结论是很清楚的:美术资源的制作流程必然会产生变革。
从目前来看,Tessellation 还属于推广阶段,需要硬件和操作系统(Vista\Win7)的普及,更需要相关的资源生产流程的确立。对网游来说,尤其是国内网游,也许是 5 年以后的事情了。
发表在 3D图形 | 留下评论

2010

2009 刚刚过去,永远不再回来。这一年发生了许多事情,这一年女儿又长大了,这一年我一直在开发第二代引擎。3 月份公司取消了次世代的开发项目,但保留了第二代引擎的开发,4、5 月份我在为一代引擎进行优化,6 月份公司取消了开发了近 2 年的游戏项目,7 月份开始开发基于第二代引擎的 MMO Demo,10 月份开始重构第二代引擎。以上就是我的 2009 的流水账。这一年最大的变化就是在这最近的两个月里,我不再像以前那样疯狂的加班,而是放慢了工作的速度,准时下班,陪陪家人,这方面我做的很不够。现在我甚至都不能理解之前的工作状态,也许是浮躁了吧,而欲速则不达,那现在呢,也许是无欲则罡,摆正了心态,随缘了吧。

我不知道以后还能否有机会在工作中开发引擎,可能只是作为业余爱好了。表面看似我有选择权,但实际上并没有 ( 这个可以有 – 这个…真没有…)。事情往往是这样的:在做一件事的时候,并不是为了这件事本身,而是为了你心中的理想,尽管这并不是你最想做的事情。11 月份跟一位业内的前辈聊的时候他说我是一个有理想的人,而且为自己当前所做的事情深感自豪。我记得从小老师就教育我们要做有理想有抱负的人,呵呵,可是老师没有告诉我们坚持心中的理想是多么的艰难。我没有刻意的坚持理想,之前只是按照自己的喜好去做事情。但现在不同,我不能这样自私下去,或者说我需要积蓄能量。我用了几个月的时间说服了自己,这其中的痛苦可想而知。做软件十多年以来,第一次坚持做一个软件3年多这么久,几乎每一行代码都有我的思想、理念、成长、学习、误解、混乱,正确的、错误的、懵懂的种种在里面,现在说要放弃,谈何容易?但是我也明白接下来几年我要做什么,要达到什么样的目标,因为黄健翔的那句经典:你,不是一个人。

人是容易懒惰的,就像宇宙里的熵,任其发展的话就是无序的,在未来的日子里,我要克服这个缺点,如果不能克服,就不能坚持,就成为随波逐流。希望 2010 在各方面都能取得好的结果,家庭,工作,以及个人。

2009,明早,我再也见不到你的日出。

发表在 Uncategorized | 一条评论

重构 Dream Engine2 资源管理

开发完 MMO Demo 之后,开始重构引擎了,首要解决的就是之前 Artists 对资源文件管理复杂的问题,而从这个问题,逐渐引伸出资源管理的问题,最后演变成为一个大规模的重构方案。

首先说明 DE2 之前的资源管理设计,在开发 DE2 初始时,并没有对资源管理进行详细的设计,所以 DE2 的资源管理基本上延用了 DE3D 的方式。具体分为以下几点:

1.资源文件结构。典型的资源有几种,比如纹理、几何数据、动画、材质、场景图节点等等,资源文件都是单独存储成文件的,这样的设计初衷是为了实现更大的灵活性,例如:不同的材质文件可以引用相同的纹理文件,不同的场景图节点描述文件可以引用相同的几何数据或者材质数据等等,但事实上在实际的游戏开发中并没有设想的那样美好,稍后将详细描述实际使用中存在的问题。

2.资源引用关系。资源和资源之间往往存在着引用关系,例如材质使用了一张纹理,场景图节点使用了一个几何数据或者是材质等,在 DE3D 中我们使用了最直观也是最容易想到的方式:资源文件中直接记录所引用资源的相对资源总目录的文件路径。

3.资源对象的管理,由每个资源管理器自己管理。资源对象的查找和生命周期管理也是由各自的资源管理器完成。资源对象是通过引用计数来控制生命周期的,当引用计数为零时,自动释放资源对象。每个资源对象在引用其他资源对象时是直接设置的,比如材质对象中包含了对纹理对象的引用,在材质中直接使用纹理对象指针。

4.异步资源加载,是通过多线程完成的。在多线程中实现资源从磁盘到 RAM,从 RAM 到 Build 资源对象的过程,当异步加载资源时指定资源创建的回调。引擎在资源加载和创建完成时进行回调。

以上基本上就是之前引擎资源管理的设计。那么,这样的设计都有哪些问题?为什么要重构?重构的方案是什么?下面一一叙述。

问题

1.资源文件数量过多。每个资源都是单独的资源文件,这种方式看似灵活,实则管理麻烦。在实际开发中,美术要面对多种类型的资源文件,纹理、几何数据、材质、动画、骨骼、场景图节点,在 DE2 中,资源类型又增加了,美术就更头大。举个例子来说,一个骨骼动画模型在 max 导出时要导出至少 5 个文件,一个骨骼动画节点描述,一个骨骼文件、一个蒙皮几何文件、一个蒙皮节点描述文件,和至少一个动画文件。在开发中需要将导出的这些资源导出到引擎指定的目录下,并且提交到服务器中,如果漏掉任意一个,都导致资源不能正常加载。当资源越来越多时,资源文件数量爆炸性增长,文件管理上的复杂性就可想而知了。这对比较感性的美术们来说,是很痛苦的事情。即使是程序员的我,在实际的使用过程中,也感觉到非常麻烦。另外,之前设计方案中的资源文件复用的灵活性,其实在开发中也体现不明显,比如为了实现纹理的复用性,美术需要将几张贴图合并为一张,但是有这几张贴图可能属于完全不同的关卡,甚至不可能同时出现在一个场景中,这样导致渲染物体的显存资源浪费,还有像复用几何文件这种情况几乎就没有发生过。其实资源的复用完全可以通过编辑器中对象级别的复用来实现,这样资源文件级别的复用在开发中显得微不足道,甚至有时因为考虑资源复用增加了美术的资源管理的复杂度。

2.记录引用资源路径的方式不灵活。这种直接记录资源路径的方式,导致资源文件路径发生改变时,引用这个资源的资源很难做出相应的调整,进而无法加载改变路径后的资源。尽管我们可以要求美术在一开始就设定好资源存储路径,但是依然不能防止这种情况的发生,而且有时侯处于项目管理的考虑,资源路径的改变是很有必要的。虽然我用了一些实现技巧,但还是没有彻底解决。

3.不能预读引用的资源信息。由于在资源文件的数据中记录了引用的资源文件路径,只能在解析资源数据过程中(或者称之为 Build 资源对象的过程中)才能知道引用的是哪个资源,这种方式,外部无法控制资源的加载顺序。此外,这种方式还造成了异步加载资源时,外部无法设置引用资源的加载方式的问题(相对于当前的加载线程是同步还是异步?)。

4.没有实现统一资源管理,缺少统一的资源对象信息描述。由于每种类型的资源由自己的管理器管理,导致资源不能用统一的方式访问和操作。

5.多线程异步加载的实现复杂。比如要求在异步读取资源时,必须指定回调函数,接口上过于严格,应用程序显示的知道了多线程资源加载的实现机制,没有做到屏蔽细节,而且回调还打乱了游戏的代码流程,需要游戏客户端单独处理回调后的流程,导致客户端实现复杂。还有些异步加载资源时的特殊情况没有处理,例如当异步读取某个资源时,主线程同步的需要这个资源对象等等。另外,由于在多线程中创建资源对象,导致资源的管理必须是线程安全,线程同步过多,降低了多线程并行的能力,这一点在 MMO 开发中表现的非常明显。

Dream Engine2 资源管理解决方案

根据上述讨论,制定出新的资源管理解决方案。

目标

1.降低资源文件管理复杂度,减少资源文件数量。

2.提供灵活的资源文件管理,当资源路径发生变化时,引擎能自动识别变化并作出相应的调整。

3.提供灵活的资源引用机制,快速获取引用资源的信息,以及资源被引用的信息。

4.实现统一的资源管理,统一资源信息描述。

5.降低多线程资源加载复杂性,提高多线程加载性能,降低线程同步次数,对游戏层屏蔽实现细节,实现灵活的加载机制。

实现

1.使用统一资源数据存储文件:资源包。资源包内部可以容纳任何资源数据,多个资源可存在于一个包内,在资源包头记录包内资源的信息,以及包内资源引用的资源信息。包内资源可继续分组,类似于文件系统机制,通过名字区分。例如包名是A,资源是 texture1,这个资源的完整名字就是 A.texture1,如果在 Group1 分组下,则为 A.Group1.texture1,如果Group1下还有分组,则以此类推,在引用资源时直接使用这个资源名。由于资源存在于包内,资源的生成只能在引擎编辑器中完成,资源的存储控制就完全交由引擎编辑器完成,这样当引用的资源存储位置发生变化时,实际上是名字发生了变化,可以通过引擎编辑器自动完成相应的改变,实现了当资源路径发生变化时能自动识别变化并作出相应的调整,无需人工干预的目的。由于在包头记录了包内资源的基本信息以及包内资源的引用资源的信息,这样只需要解析包头就可获取相关的资源信息,也实现了快速获取引用资源信息的目的,游戏逻辑可以预读资源包信息即可构建完整的资源信息。同时,资源包的设计实现了降低资源文件管理的复杂度和减少文件数量的目的。

2.资源数据库。引擎中实现资源数据库机制。通过分析发现,资源和资源之间往往存在着引用关系,这种关系实际上是构成了 DAG(有向非循环图)这样的数据结构,每个资源节点都可以引用多个资源,同时又会被多个资源引用,但不会形成循环引用关系,所以我在引擎中实现了这样的资源数据结构,我称之为 Resource Info DAG,图中每个节点并不是资源对象,而是资源描述,因为资源可能随时被使用,资源描述可以一直存在于整个游戏过程中而不必被删除(当然想删也可以,只是需要这个资源时还要重新读一次资源包头)。资源信息是轻量的数据,只是记录了资源的类型、名字(我们实现了名字系统,一个整形即可代表一个完整的名字)、资源大小、在资源包中的数据偏移(用于资源加载)、所在的资源包、所引用的资源名字列表以及真正的资源对象指针。同时,为了快速索引到需要的资源,只有这样一个 DAG 是不够的,还需要一个名字和资源信息的映射列表,这样,Resource Info DAG 和资源映射表,就形成了引擎中的资源数据库。资源信息的出现实现了统一资源信息描述的目的,而且为实现统一资源管理铺平了道路。

3.改进资源的异步加载框架。异步资源加载可以是单线程也可以是多线程,无论在哪个线程,流程应该是一致的。在引擎中存在一个资源预读列表,以及资源预读管理器,当预读一个资源时,通过资源数据库可以立即知道资源是否存在,如果不存在则先加载其引用的资源(在资源信息中已经记录)。预读可以是阻塞的,也可以使分时间片的方式,具体的方式允许外部在预读资源时设置。时间片方式目前只是实现到资源的粒度,如果一个资源超过了读取限制的时间,则继续读直到完成为止,如果没有超过则预估下一个预读的资源时间,如果够则读,不够则进入到下一帧。游戏层可以自己设定预读资源,真正需要资源时直接通过资源数据库获取,而且往往此时资源已经被创建好,如果没有,则同步创建。应用层也可以预读一些基础资源,比如玩家相关的模型、动画等等,和逻辑数据相关的最好能预读,无关的即使需要时再读都可以,而且是完全异步,比如纹理、声音等。这样的改进带来的好处就是游戏层不需要关心资源加载的实现细节,甚至不需要向引擎提供回调(需要时也可以设置回调),需要资源时直接通过资源数据库接口获取,简化程序的复杂性。而且游戏层可以根据自己的需要预读资源,资源的调度上也很灵活。

资源的多线程加载只是完成从磁盘到内存的过程,并不包含资源对象的创建过程,引擎规定所有资源对象的创建都是在主线程中完成的,不允许在其他线程创建资源对象,这样就完全消除了资源创建中线程同步的问题。实际上我们知道,最耗费时间的往往是磁盘 IO 读取操作,而资源数据一旦进入到内存,资源的解析和构建过程是很快的,即使放在逻辑线程中同步创建资源对象,开销也不大。而且在渲染线程独立的情况下,逻辑线程的负担就更少了,同步创建游戏对象已经不是主要的性能瓶颈。多线程只是 IO 读取的话,实现上就简单了许多,首先就不需要考虑资源对象管理上的线程同步问题,其次可以利用引擎已有的基于 Ring Buffer 生产者/消费者线程机制实现一个完全无锁的多线程 IO 读取框架。不过有种情况还是要特殊处理:当多线程 IO 读取某个资源数据时,主线程同步的需要创建这个资源对象,这种情况下需要同步IO读取。但实际上这种情况属于低概率事件,即使偶尔发生用户也能接受。

发表在 Uncategorized | 留下评论