2009年1月31日星期六

Nebula3多线程架构

在Nebula3中,代码在两种不同场景中并行运行。第一种场景我们称为“Fat Threads”。一个Fat Thread在特定的cpu内核上运行一个完整子系统(像渲染,音频,人工智能,物理,资源管理)。

另一种类型的线程我们称为“Job”。一个Job就是一块数据和处理该数据的c++代码。Job对象由job调度器来处理,把job分配到低负荷的cpu内核上以尽量保证cpu内核处于忙碌状态。

设计一个完整的系统并以某种方式保证在任何时候所有核心处于忙碌状态,就目前来讲当然是一种挑战。

这就是我期待有更多的试验和更好的调优。

第二个挑战是让程序员的生活过得尽可能地简单。一个游戏应用程序员在任何时候都不需要关心他运行在一个多线程环境中。他不必担心创建一个死锁或者覆写其它线程数据。他也不需要关注临界点,事件和信号量。并且整个引擎架构是健壮的。某种意义上大部分传统多线程代码是脆弱的,那是因为会发生资源竞争或者忘记了临界条件会破坏数据。

当需要共享数据或者两个线程间需要发生通信时多线程就变得难于预料了。

从大的范围看,Nebula3是通过叫做“并行Nebulas”的概念来解决这两个问题的。方法是每个Fat Thread都拥有自己的最小运行库来运行一个完整的子系统,其中包含该子系统所需的所有组件。因此如果一个运行在它自己线程中的子系统需要操作文件,那么它就单独拥有一个文件服务子系统。这种解决方案的优点是,在nebula中的大部分代码不会意识到自己运行在一个多线程环境中,并且所有Fat Thread之间不需要共享数据。每个nebula核心完全独立于其它nebula核心运行着。缺点当然是有的了,因冗余的数据而浪费一些内存,但紧紧是几K,而不是几M。

这些冗余的数据消除了精细的线程锁,让程序员不必要为了多线程的安全去考虑每一行代码。

但当然了,Fat Thread之间会发生通讯,不然整个设计概念就没用了。这里的想法是建立一种并且仅仅只有一种标准的系统通讯方式,并且这种通讯是可靠和快速的。这就是消息

系统的由来。只能通过发送消息来跟Fat Thread通讯。消息是带着一些数据简单的c++对象,并有一些set/get方法。通过这种方式通讯,只有消息子系统的代码才需要线程安全(访问消息携带的资源,如内存缓冲区,必须受到限制因为它们是共享数据)。

这个方法解决了Fat Thread场景中大多数多线程问题,但是对于解决job对象问题却没有任何帮助。Nebula3在某些地方需要严格限制job对象可以做什么或者不能做什么。最直接的限制是job在不能内存缓冲区内做计算。以这种方式,jobs就不需要复杂的运行库(不使用文件IO,不需要访问渲染模块等等)。如果这还不够的话,一个“job运行环境”必须被定义,就像Fat Thread那样。job不能开启一个线程,它只能被线程池中的线程调度。这在运行开销区间内不会有太大的问题。

到目前为止,只有Nebula3 IO子系统作为概念证明实现了Fat Thread,并且运行得很不错。如果要做传统的同步IO操作,Nebula3只要简单地直接调用本地线程的IO子系统就可以了。因此,例如要列出文件夹里的内容或者是删除文件,只要简单地调用一个c++方法就可以做到。对于异步IO操作,一些通用的IO操作已经被很好定义为消息了(i.e. ReadStream, WriteStream, CopyFile, DeleteFile, etc...)。做异步IO操作也仅仅需要几行代码就可以了:创建一个消息对象,填入数据,把消息发送到一个单例的IOInterface上。如果需要,可以等待或者是轮询这个异步IO操作。

这样的好处是,整个IO子系统不需要多线程相关的代码,因为在各个Fat Thread中的IO子系统是完全独立的(当然了,在某些点上还是会发生同步的IO操作,但把这些统统留给操作系统来完成)。

原文: Nebula3 Multithreading Architecture

[声明]:限于译者水平,文中难免错漏之处,欢迎各位网友批评指正;

没有评论:

发表评论