gorden曹的地盘

 

现实中的多线程(一)

理论如何转化为实践?在我其他的blog里我讨论了各种方式来实现并发,其中就包括基于所有权的类型系统.现在我面临一项任务:将这些方法应用到现实的软件产品Code Co-op中.客户要求添加更多的并发性,从而使Code Co-op的dispatcher(分发器)部分响应更迅速.因为使用的语言是C++(译者注:就是说没有”基于所有权的类型系统”),所以实现的时候需要遵守严格的纪律而不能依赖类型系统.

如果可能,我还是喜欢用消息传递技术(message passing),但是,由于性能的原因,我发现自己经常被迫使用锁来操作共享状态(shared state). 我将在下面讨论message passing和sharing之间的各种权衡.欢迎读者分享自己的观点,能提出更好的实现就更好啦.

写这些代码时,我时常想,如果可以用D programming language来写该多么简单——它支持immutable(不变)和shared(共享)类型(译者注:见文章开头的”基于所有权的类型系统”,不变类型和共享类型是D语言提供的两种类型系统.本文作者似乎是D语言的作者之一),更不必说garbage collection啦.但是D强烈的偏好message passing,结果就像Erlang那样把解决办法限制在值类型和不变对象上了.这里,我的C++实现既允许在线程间直接共享数据结构,同时又可以传递非值类型的消息.欢迎D语言社区参与讨论.

dispatcher是怎么工作的

dispatcher是隶属于Code Co-op的一个程序,功能是将各种差量(deltas)(我们称它们scripts)分布到版本控制项目的本地和远程成员.scripts被放置在一个特殊的文件夹,每当这个文件夹被修改,dispatcher就会接到通知.scripts会把目录(译者注:指Code Co-op工作的那个目录,而不是上文提到的那个放scripts的目录)列出来,并且创建一个容器来包含这个目录的文件名.它之后会读取每个文件的文件头,找到对应该script的intended recipients(译者注:这个不知道是什么,反正是一个处理列表的处理器).它会在自己的数据库中查找recipients并将它映射到destinations(目标). Destinations就是一个path(路径)或者email地址.path可以是本地的也可以是远程的,比如\computername\sharename.考虑到本文的主题,我将专注于网络path.

当解码文件头的时候,dispatcher会为每个script创建2个数据结构:一个是immutable的script的描述称为ScriptInfo.它包括永远不会变化的数据,像文件路径, recipients列表等等.另一个称为ScriptTicket的,是mutable(可变)的,它包括一个指向ScriptInfo的指针,一堆标记进度的flags,和一个复制请求(copy requests,译者注: 从上下文推断,Code Co-op是一个类似svn的版本控制软件,copy request就是将文件复制到某处的概念,比如创建分支的时候,会一次复制n个文件)本文作者)的列表——每个请求对应一个destination(这里就是网络路径).注意C++没有immutable的概念,所以处理ScriptInfo的时候需要由程序员来保证它的不变性.

图表 1 ScriptTicket包含可变状态:一个copy requests数组和一个指向不变的ScriptInfo的指针

简单的说,dispatcher必须遍历所有的ScriptTickets,在其内部遍历ticket的copy requests,执行复制操作,如果成功,那么物理的在该copy request对应文件的文件头上做标记,以避免对一个网络路径复制多次(译者注:可能是避免_requests里面有相同的copy request).只有当所有的ScriptTickets的copy request都成功之后,对应的该script才被删除.

这里有个重要问题:script复制(译者注:把script里面指示的文件复制过去而不是复制script本身),尤其是网络复制,可能会非常慢.我们不想让dispatcher在复制的时候完全失去响应,所以我们需要一个独立的worker线程来异步分发scripts.自然main线程必须能和worker线程通信,而且是双向通信.让我们看看这个同时需要共享和传递的数据结构.

线程间通信对象

不变对象ScriptInfo可以安全的在线程间共享而不需要同步. ScriptTicket不一样,它是可变的.但是幸运的是它是值类型,所以可以被安全的当成值来传输(译者注:比如C语言的参数就是值传递,传递一个struct也是把整个struct复制一份传递的).

但是ScriptTicket不仅仅是一个平展的结构——它还包含指针.一个包含指针的数据结构只有被深copy才可以被传递,就是说,不仅是指针本身需要复制,连指针指向的对象也一样需要递归的复制下来.但是,线程安全的传递也不是必须要深copy的,就像图示的ScriptTicket那样:

一个ticket保存了一个指向ScriptInfo        的指针,但是因为ScriptInfo是不变的,所以深copy不是必要的.

ScriptTicket同时也包含了一个(可变的)copy requests的容器.C++标准库中,容器本身都是值类型的(C++ Standard Library containers of value types are also value types,原文的意思是值类型的容器才是值类型的,我以为不准确,正确的说法是,容器都是值类型的,但是容器的内容不一定是值类型的),尽管它们是基于指针的.它们的构造函数和赋值操作符会执行深copy.(D在这方面有问题:包含指向可变数据的指针的对象不能被当作消息在线程间传递)

还有一个问题:ticket对象包含一个指向共享monitor(一个自身包含锁,所有方法也都用这个锁来同步的对象).这个monitor供(图中没有显示)被dispatcher的UI部分使用,并且需要所有正在处理的scripts的状态的变化.当这个monitor被正确实现之后,就可以安全的在ScriptTicket——1个”值传递”的对象——里面嵌入一个(浅)指针.这里我们超出了值传递的语义,但是并没有牺牲线程安全性.

注意ScriptTicket可以被术语”所有者”类型(ownership types)来描述.一个ticket的所有者总是当前线程(owner::thread),可变的容器 _requests的所有者是这个ticket,当然了,也就同样属于当前线程.ticket里面的ScriptInfo是不可变的(owner::immutable),用于UI通信的monitor被”self”所拥有(owner::self).后面2个可以被线程间共享.

尽管script tickets(译者注,作者在这里没有使用ScriptTicket,应该是指ScriptTicket类生成的对象)在线程间按照值传递,但是在同一个线程内部他们经常被按引用传递——这不仅是性能原因,还因为调用者想知道被调用者所做的修改.在基于所有权的(ownership-based)系统里面,这种引用被标记为”借用”(lent),而且系统将保证这个对象没有别名(aliases,译者注:即其他的引用)泄露到外界去.

posted on 2011-08-05 00:34  Raffaele曹  阅读(422)  评论(0编辑  收藏  举报

导航