这一天,与流程无关,是多线程库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线程。