查看: 3791|回复: 3

OSG原创教程:最长的一帧(11)

[复制链接]

该用户从未签到

发表于 2008-8-25 13:17:03 | 显示全部楼层 |阅读模式
当前位置:osgViewer/Viewer.cpp第889行,osgViewer::Viewer::updateTraversal()
上一日的“解读成果”中并没有标明updateTraversal函数,原因很简单:因为有关这个函数的解读还没有结束。至于是哪些问题还没有得到解答,相信您也心知肚明了,对,就是DatabasePager!它的工作原理,流程,以及在场景图形中起到的作用。也许您早已对此烂熟于胸,也许正好也想把这个有名的OSG工具剖析剖析,那么机会来了:就让我们一起用上两到三日的时间,仔细地解读一下这个所谓“分页数据库”到底是什么来头。

“悬疑列表”中的那些数据好像沉积了很久了……不过还是再忍耐一下,不要担心会不会变质或者无法兑现的问题(^_^)。

在解读DatabasePager之前,我们或许需要一点预备知识:也就是OpenSceneGraph中的线程处理库OpenThreads。

面向对象的跨平台线程库OpenThreads原本是独立的开源工程,OSG 2.x以后的版本将其纳入了自己的体系结构当中,成为OSG基本库的一份子,目前最新的版本为2.3。

OpenThreads库包含了以下几个最主要的线程处理类:

Thread类:线程实现类。它是一个面向对象的线程实现接口,每定义一个Thread类,就相当于定义了一个共享进程资源,但是可以独立调度的线程。通过重写run()和cancel()这两个成员函数,即可实现线程运行时和取消时的操作;通过调用start()和cancel(),可以启动或中止已经定义的进程对象。

Mutex类:互斥体接口类。如同pthread等常用的线程库那样,OpenThreads也提供了互斥体操作的机制,它有效地避免了各个线程对同一资源的相互竞争,即,某一线程欲操作某一共享资源时,首先使用互斥体成员的lock()函数加锁,操作完成之后再使用unlock函数解锁。一个线程类中可以存在多个Mutex成员,用于在不同的地点或情形下为共享区域加锁;但是一定要在适当的时候解锁,以免造成线程的共享数据无法再访问。

Condition类:条件量接口类。它依赖于某个Mutex互斥体,互斥体加锁时阻塞所在的线程,解锁或者超过时限则释放此线程,允许其继续运行。
这里涉及了几个线程操作中重要的概念:同步,阻塞以及条件变量。线程同步,简单来说就是使同一进程的多个线程可以协调工作,例如让它们都在指定的执行点等待对方,直到全员到期之后才开始同步运行;拥塞,即强制一个线程在某个执行点上等待,直到满足继续运行的条件为止。例如其它的线程到达同一执行点,某个变量初始化完成等等,可以通过条件变量来设计各种条件。

Block类:阻塞器类。顾名思义,这个类的作用就是阻塞线程的执行,使用block()阻塞执行它的线程(注意,不一定是定义它的Thread线程,而是当前执行了block函数的线程,包括系统主进程),并使用release()释放之前被阻塞的线程。
下图所示的代码实现了一个最简单的线程,并演示了Block类的使用方法。运行程序后可以发现,Block::block()函数将首先阻塞主进程,被释放后再次阻塞的是TestThread线程,这与它是谁的成员变量并无关系。
附图1

BlockCount类:计数阻塞器类。它与阻塞器类的使用方法基本相同:block()阻塞线程,release()释放线程;不过除此之外,BlockCount的构造函数还可以设置一个阻塞计数值。计数的作用是:每当阻塞器对象的completed()函数被执行一次,计数器就减一,直至减到零就释放被阻塞的线程。

Barrier类:线程栅栏类。这是一个对于线程同步颇为重要的阻塞器接口,它的构造函数与BlockCount类似,可以设置一个整数值,我们可以把这个值理解成栅栏的“强度”。每个执行了Barrier::block()函数的线程都将被阻塞;当被阻塞在栅栏处的线程达到指定的数目时,就好比栅栏无法支撑那么大的强度一样,栅栏将被冲开,所有的线程将被释放。重要的是,这些线程是几乎同时释放的,也就保证了线程执行的同步性。

注意BlockCount与Barrier的区别,前者是由其它任意线程执行指定次数的completed()函数,即可释放被阻塞的线程;而后者则是必须阻塞指定个数的线程之后,所有的线程才会同时被释放。

ScopedLock模板:这个模板是与Mutex配合出现的,它的作用域之内将对共享资源进行加锁,作用域之外则自动解锁,代码格式如下:
  1. {
  2.   OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
  3.   ……
  4. }
  5. ……
复制代码
在大括号范围内,进程的共享资源被当前进程锁定,超过范围则自动解锁。

当前位置:include/osgDB/ DatabasePager第250行,osgDB:: DatabasePager::updateSceneGraph()
updateSceneGraph函数的工作是更新分页数据库的内容,它的内容简单到只包含了两个执行函数的内容:
  • 1、DatabasePager::removeExpiredSubgraphs:用于去除已经过期的场景子树;
  • 2、DatabasePager::addLoadedDataToSceneGraph:用于向场景图形中添加新载入的数据。


有关这两个函数的内容介绍我们先放在一边,因为现在即使通读它们的内容,恐怕也很难找出什么有用的线索来。DatabasePager类的那些繁多的成员变量就已经让我们应接不暇了,有必要分出主次,放弃阅读那些没有对主要功能直接做出贡献的变量和函数内容,才能够顺利完成这次的任务。

那么,我们首先来了解一下DatabasePager的概念。

数据库的分页技术在很多领域都十分常见。例如网络上的海量数据库搜索,往往会采取分页的方式,只搜索数据库的一部分内容;当用户浏览到后面的页面之后,再继续搜索对应的内容。
在三维场景的浏览中同样会面对这个问题,如果用户需要浏览的数据量很大,比如地形模拟,虚拟小区和城市,甚至是虚拟地球的工程中,都不可避免地要使用到场景数据库的分页技术,否则将对计算机系统产生极大的负担。

在OSG中, osgDB:: DatabasePager类执行的就是这一工作:每一帧的更新遍历执行到updateSceneGraph函数时,都会自动将“一段时间之内始终不在当前页面上”的场景子树去除,并将“新载入到当前页面”的场景子树加入渲染,这里所说的“页面”往往指的就是用户的视野范围。这些分页和节点管理的工作如果由渲染循环来完成的话,恐怕是费时又费力的,对于场景的显示速度有较大的影响,因此,DatabasePager中内置了专用于相关工作处理的DatabaseThread线程,也就是我们下一日将要讲解的主要内容。

解读成果:
OpenThreads库。
悬疑列表:
如何调度和实现OSG的多线程机制?什么是“渲染目标实现方式”?

[ 本帖最后由 array 于 2008-8-25 13:18 编辑 ]

附图1

附图1

该用户从未签到

发表于 2008-8-25 16:26:52 | 显示全部楼层
顶,正需要解决海量数据的现实问题,搬个板凳来听课
谢谢老大

该用户从未签到

发表于 2008-9-25 15:33:54 | 显示全部楼层
顶,正需要解决海量数据的现实问题,搬个板凳来听课
谢谢老大

该用户从未签到

发表于 2008-9-27 15:17:46 | 显示全部楼层
再顶;
虽然我没有完全理解;
但是对于openThread这个东西有点理解了;
您需要登录后才可以回帖 登录 | 注册

本版积分规则

OSG中国官方论坛-有您OSG在中国才更好

网站简介:osgChina是国内首个三维相关技术开源社区,旨在为国内更多的技术开发人员提供最前沿的技术资讯,为更多的三维从业者提供一个学习、交流的技术平台。

联系我们

  • 工作时间:09:00--18:00
  • 反馈邮箱:1315785073@qq.com
快速回复 返回顶部 返回列表