调试最长的一帧(第11天)

这一天,与流程无关,是多线程库OpenThreads,第三天已经分析过了,本质上就是windows核心编程中的事件,临界区和信号量,存储区和构造时加锁,析构时解锁的类。

再看看这个库是怎么封装的。

抄抄。

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

从头文件上猜测个大概,但是具体还要看源码。

上边说明,concurrency的设置和获取,没有起到实际作用。可能新版本会有

上边得解释解释,因为不管怎么封装,已经接触到底层,开启线程了,Thread::startThread()其实就是调用的Thread::start(),只要_prvData存在,否则就返回0了

看看构造函数中,

如果生成了新的Win32ThreadPrivateData指针,应该就OK了。当然是不是,目前只是猜测,还得后面调试才知道。

Thread::start()看起来应该是这样子,开启线程后阻塞,直到线程转起来才不再阻塞。

跟过去,剥离封装

看来对这个_release设定为false,就是block.reset()的作用

看来

就是将新开的线程handle,赋值给pd->tid._handle了。

很明显

看来block()是等待。

果然到waitobject系列了

等待完后,就设置事件

这个也好理解,因为构造函数就创造事件了。

线程handle在pd->cancelEvent里面,并且存储在Thread->_prvData里。

可以看到block是在_prvData里面的。

 

回头看看等了个啥。

在构造函数中,初始化信号量和事件

这个有多少个等待者呢?waiters_

这个大概清楚了,实际上这个Win32ConditionPrivateData类就说明了个事件和信号量的联合使用。

等待成功的话,

就把waiters_-1,

如果没有等待者了,设置事件。大家该干啥干啥了,

这里还要有个was_broadcast_要为真才行.

 

可知在广播时,如果有_waiters时,才有这个was_broadcast_=1;

那什么时候Win32ConditionPrivateData::broadcast()呢

 

可知,是Condition::broadcast()调用

再往上看

block::release()中

block::completed()中

在block::reset()中

在block::release()中

在Barrier中,可以看到,与线程个数相关,才进行广播.

不要死记硬背。如果每个类都去背,要去背多少东西,还容易混淆。

拉回来,扯远了,现在还得继续看Thread::start(),至少已经知道了,。接下来。

 

但是,刚才有个错误的地方,那就是Thread的构造函数创建事件,和Win32ConditionPrivateData类中的setEvent()没有配对关系。那么和谁配对呢?

可知,在Thread::cancel()里面。构造函数里创建的也是这个pd->cancelEvent,这俩才是配对的。

也就是说,Thread::cancel()之后,后面的东西就可以玩了。

 

继续看下去

类似于c++11风格的join()

这里涉及到了cancelMode,什么东东,看看声明

肯定有应用场景

可以看到,可以人为设置

再看一下

看看thread的应用场景

往上回溯

 

再往上走

再往上走

涉及到了分页数据库DataBasePager,分页图像库ImagePager,ViewerBase,单视景和多视景。看来至少3个线程要开

看看用到了哪些方法

分页数据库requestNodeFile()

分页图像库requesImageFile()

viewer中的realize(),这里其实就开线程了,只是没有看并行堆栈。

viewerbase中startThreading(),应该与摄像机背后的消息队列相关..

多视口也是realize(),

 

心里有个大概印象,一会调试时再深入。

拉回来。该是Mutex类,电子书上说是互斥体类,我个人认为应该叫临界区类,因为是用户级别的,而不是内核级别的。不过这不是关键。作用是避免了各个线程对同一资源的相互竞争,即,某一线程欲操作某一共享资源时,首先使用lock()函数枷锁,操作完成后用unlock()解锁。一个线程类可以存在多个Mutex成员,用于再不同地点或情形下为共享区域加锁;但是一定要在适当的时候解锁,以免造成线程的共享数据无法再访问。

看看代码

这个类稍微少点,因为主要是加锁和解锁的作用,看看实现代码

 

先看友元类Win32MutexPrivateData,因为与锁相关,构造函数时进入临界区,析构时删除临界区,类似于ScopedLock(),函数内专用,不怕忘了解锁。

 

现在看看Mutex类,有临界区就用临界区,没临界区就用数值原子操作的方式

 

 

下一个类是Condition类,条件量接口类,它依赖于某个Mutex互斥体,互斥体加锁时阻塞所在的线程,解锁或者超过时限则释放此线程,允许其继续运行。

继续抄一抄,加深印象。

线程操作的几个重要概念:同步,阻塞以及条件变量。线程同步,简单来说就是使同一进程的多个线程可以协调工作,例如,让它们都在指定的执行点等待对方,直到全员到后才开始同步运行;阻塞,即强制一个线程在某个执行点上等待,直到满足继续运行的条件为止。

其实,这个很简单,好比10个运动员去跑步,跑之前,发令员让他们先去拿牌子,发牌子的人一次发一个,运动员拿完牌子就在跑道等着。发令员等到都准备好了,发牌子的人说,牌子发完了,没有等待的人了,然后一开枪,运动员开始跑步。

拿牌子时要对牌子上锁,一次发一个牌子,等待拿牌子的运动员人数也少一个,这个是信号量。或者广播,有多少的运动员等待的就直接全发掉。不管哪种方式,运动员要等待发牌子。拿完牌子后就在跑道等着开枪事件触发。

发令员让运动员拿牌子是创建一个跑步事件CreateEvent(),还没有触发;等到获得牌子发光了得时候,就开枪触发跑步事件。setEvent()

看看源码,与Condition相关的也有数据类Win32ConditionPrivateData

广播与单播

 

也就是说 Win32ConditionPrivateData类的wait()作用,不论单播还是广播,都要等到所有的运动员拿到牌子为止,然后触发声明拿完牌子事件。

现在看看Condition类代码,它要调用Win32ConditionPrivateData类

从实现函数可以看到,Condition类只是简单地传递了一个外部的锁,对Win32ConditionPrivateData类的简单调用而已。

 

 

接下来看Block类,抄一抄电子书上的介绍,有个大致印象。

Block类:阻塞器类,即阻塞线程的执行,使用block()阻塞执行它的线程,(注意,不一定是定义它的Thread线程,而是当前执行了block函数的线程,包括系统主进程),并用release()释放之前被阻塞的线程.

从上面可以看到,reset()和set()只是确定一个标志位_released是否被释放。而block()阻塞是conditon的wait(),从前面得知,是进一步调用了

在这里等着。

当block类释放线程时,

调用

对于waiters_,构造函数时是0,

因为在等待时waiters_添加了一个,

所以,waiters>0,在广播时,

进入w>0分支,was_boradcast=1;have_watiers=1;

进而进入以下分支,递增信号量的当前资源计数,并等待waiters_done_

,此时wait()则等到了,waiters_=0,was_boradcast=1从而设置事件waiters_done_

broadcast()等到了waiters_done_事件,从而往下走,was_boradcast=0,返回函数,不再阻塞。

电子书上有个block的例子,调试下。

GO!

开始线程

开线程了

传参传的自己,this,

阻塞等待

终于开线程了

设置存储区数值和线程优先级

 

 

释放主线程

有一个线程(主线程)在等待,走的分支

递增信号量资源计数,等待waiters_done_

 

由于信号量资源记数增加,所以,主线程等待到了。

 

不让主线程阻塞后,进行新线程运行

 

主线程释放,正常运行

 

阻塞新建线程的_operator

阻塞新建线程的_operator,并等待

 

 

阻塞_operator直到输出10

_count=10时,_operator释放

 

 

 

结束线程要跳出循环,起作用的时_done = true;

 

 

总结下,

1,主线程生成新的线程并开启,然后阻塞主线程,

2,新线程开启后,释放主线程,并运行

3,主线程不再阻塞后,阻塞新线程的block成员变量_operator;每个_operator阻塞时,先将waiters添加1,然后等待

 

 

4,新线程运行满足到一定条件时,广播释放_operator,先看_operator的waiters是否>0,如果有等待的,则递增信号量当前资源计数waiters个,调度正在阻塞的每个_operator,并同时阻塞自己,等待_operator的等待结束事件waiters_done_。每个_operator等待到这个信号量后,把waiters-1,如果_waiters=0,且广播了,说明每个_operator都等待结束,则触发等待结束事件waiters_done_,。新线程等到到这个事件后,知道所有的_opeator都等待到了,就不再阻塞自己,继续运行

 

5,_operator重置_release成员变量,再重新阻塞,在主线程输入值,切换到主线程工作

6,主线程工作完毕后,终止新线程,先解除新线程的阻塞,再判断新线程是否仍然在运行

 

拉回来,现在学习blockcount类,抄抄。计数阻塞器类。它与阻塞器类的使用方法基本相同,block()阻塞线程,release()释放线程,除此之外,blockcount的构造函数还可以设置一个阻塞计数器值。计数的作用是:每当阻塞器对象的completed()函数被执行一次,技术器就减一,直至零就释放被阻塞的线程。

看看源码

可见看到,_currentCount=0时,释放线程。_currentCount>0时,阻塞.。

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

看看源码,不死记硬背。

先是有关数据Win32BarrierPrivateData类

 

 

 

很明显,blockCount是由其他任意线程指定次数的complete()函数,即可释放被阻塞的线程;而Barrier是必须阻塞指定个数的线程后,所有的线程才会同时释放。一个是阻塞次数,一个是阻塞线程数

ScopedLock模板,这个模板是与Mutex配合出现,它的作用域之内将对共享资源进行加锁,作用于之外则自动解锁

看看源码

还有个相反的模板,构造函数解锁,析构函数枷锁

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

在osg中,osgDB::DatabasePager类执行的就是这一工作:每一帧的更新便利到updateSceneGraph函数时,都会自动将一段时间内始终不在当前页面上的场景子树去除,并将新加入到当前页面的场景子树加入渲染。这里所说的页面往往指的就是用户的视野范围。这些分页和节点管理的工作由专门处理的线程DataBaseThread线程。

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值