当某一个线程需要对一段代码或者数据进行访问的时候,为了保证不会有对个线程同时访问这段代码或者数据的情况,都会使用锁来做互斥。
线程A完全访问完代码(下面只说代码,数据是一样的)的时候,线程B才可以访问。如果线程B访问的时候,线程A正在访问,这个时候线程B会阻塞。太多的阻塞和锁会让效率下降。
仔细想一想,某个线程A访问一段代码到底是什么意思?
我的理解是当前整个系统状态下需要这段代码进行一次执行。
再继续想,难道我必须要求这段代码马上(相对于这个线程)执行吗?完全可以把这段代码的执行做成一个任务,丢到某个专门的线程C中执行。线程C循环查看队列中是否有需要执行的任务,当它看到线程A发送的任务后先执行,然后再回调线程A,这个回调也可以采用线程队列的方式,即把这个执行结果发送到线程A的队列中。这样各个线程只通过自己的消息队列互相协作,大大降低了耦合性。
这种模式也有很多不足的问题,比如线程A需要马上获悉这段代码的执行结果,然后下面的执行逻辑依赖于这个结果。
不过个人认为这种多线程的设计是不够好的,总是可以有办法来避免这样。退一步说,如果确实存在线程A需要马上获取执行结果的情况,也可以处理。线程A把这个任务设置优先级很高,然后就不断循环判断线程C是不是执行完这个任务了。假如这个时候线程C正在执行一个很耗时的任务,甚至可以让线程C先暂停当前任务的执行,执行完线程A的任务才去执行。这种情况不多见,主要会出现在一些实时系统中。
在我们游戏中,主要有两个线程。逻辑线程负责执行相关的脚本逻辑和生成渲染数据;渲染线程负责渲染和OpenGL接口调用。这两个都是循环线程。
当需要生成一个gl纹理的时候,逻辑线程获取相关图像数据,然后分派一个任务到渲染线程。渲染线程循环查看消息队列,发现有生成纹理的任务就执行,然后把结果放到逻辑线程的消息队列里面。
当需要渲染一组对象的时候,逻辑线程也使用相关数据生成一个任务放到渲染线程。渲染线程还可以定义优先级,决定是先渲染还是先生成纹理。
当渲染线程比较忙碌而逻辑线程比较空闲的时候,是不是一些计算可以放到逻辑线程中,毕竟玩家感受到的帧率取决于二者的最小值。
总之,这种消息队列的做法降低了耦合,增加了灵活性和健壮性。
把线程的概念抽象到一个更高层次的任务的概念,线程对代码的执行上升到线程需要某个任务一次执行的高度,看问题的层次高了,也会发现很多更优秀的设计。