在这一章中,我们试图建立一个通用的术语来定义Akka.NET中并发,分布式系统等概念,作为讨论的基石。请注意, 其中很多术语并没有统一的定义。 我们只是在Akka.NET文档中来使用。

并发 vs. 并行

并发和并行是如影相随的概念。 并发意味着两个或两个以上的任务正在进行中,即使他们可能不会同时执行。例如,有的任务顺序执行,有的任务通过时间片的切换与其他任务交替执行。 而并行是真正的同时执行。

并发(Concurrency)

Concurrency

并行(Parallelism)

Parallelism

异步 vs. 同步

如果调用者在这个方法返回一个值或抛出一个异常后才能继续执行,则称之为同步调用。另一方面,异步调用允许调用者在方法完成之前就继续执行,等到这个方法完成后通过额外的机制来通知调用者(它可能是一个回调方法,一个Future方法,或发送一个消息)。

一个同步API可以使用阻塞来实现同步,但这不是必需的。一个CPU密集型的任务可能有类似的行为作为阻塞。在一般情况下,它优先使用异步API,因为它们保证系统能够继续执行。Actors天生就是异步的:一个Actor发送消息后会继续执行而不等结果。

非阻塞 vs. 阻塞

我们谈论阻塞(blocking),一个线程的延迟也许能无限期的延迟一些别的线程。比如一个线程通过互斥独占使用一个资源。如果这个线程无限期的持有这个资源 (如不小心写了个死循环) ,那别的(打算使用这个资源的)线程只能等到天荒地老而不能进行下去。相比之下,非阻塞(non-blocking)意味着没有线程能够无限期的延迟别人。

非阻塞操作比阻塞的好。因为包含阻塞操作的系统对整体的进度并不能有效的保证。

死锁 vs. 饥饿 vs. 活锁(Live-lock)

当几个参与者彼此等待对方达到一个特别的状态,以便能够继续进行,死锁产生了。 因为要达到一个特别的状态必须能够继续进行,要继续进行反过来又要达到特别的状态 (“两难”问题) ,所有受影响的子系统都会停摆。死锁和阻塞息息相关, 一个参与者线程能够无限期延迟其他线程的进行必然产生死锁。

在死锁的情况下,没有参与者能够继续进行, 而相比之下饥饿(Starvation )发生时, 有的参与者能继续进行,有的却不能。 典型的情况是 一个单纯的调度算法总是选择高优先级的任务,而忽略低优先级的。如果传入的高优先级的任务源源不断,那低优先级的任务将永远没机会完成。

活锁和死锁类似,没有参与者能取得进展。不同的是参与者不会僵在那里等待别人的进行,而是不断的改变其状态。 一个示例场景是当两个参与者有两个相同的资源实例可用。 他们每一个人都试图获取资源, 但他们也检查别人是否需要这个资源。 假如发现其他参与者已请求这个资源,他们尝试去获取另一个资源实例。 可能发生比较悲剧的情况是两个参与者被两个资源弹来弹去,都不能获取到它,总是被礼让给另一个。

争用条件(Race Condition)

假设一系列事件的顺序遭受外部非确定性因素的打乱时发生了争用条件。 当多个线程有一个共享的可变状态,并且线程对状态进行交替操作可能导致意想不到的行为,这时经常引起争用条件。虽然这是一个常见的情况,共享状态是没必要有争用条件。 一个例子是客户端发送无序数据包 (如 UDP 报文) P1, P2 到服务端。当数据包通过不同的网络路由传送, 服务端可能先接收到P2,再接收到P1。如果报文不包含发送顺序的信息,服务端是不可能知道P1和P2的顺序。 根据数据包的意义这可能会导致争用条件。

注意

Akka.NET 提供了对给定的一对 Actors 之间发送消息的唯一保证是,它们的顺序总是保持不变。 见 消息传递的可靠性

非阻塞担保 (Progress Conditions)

正如前面几节讨论的阻塞是不受欢迎的几个原因, 包括死锁和系统吞吐量降低的危险。 在以下部分中,我们将讨论各种非阻塞特性的不同优点。

Wait-freedom(无等待?)

如果每次调用都保证在有限步骤内完成,那这个方法就是wait-free的。 如果一个方法是有界的wait-free,那么步骤的数量有一个有限的上限。

从这个定义来看,wait-free的方法永远不会阻塞,因此不会发生死锁。此外, 因为每个参与者能在有限步骤之后继续进行 (当调用完成), wait-free方法均是free of starvation。

Lock-freedom(无锁)

Lock-freedom 比 wait-freedom 有更弱的特性。 在 lock-free 调用的情况下, 一些无限的方法常常在有限的步骤完成。 这个定义意味着lock-free调用不发生死锁是可能的。另一方面, 保证一些调用在有限数量的步骤完成是不足于保证他们最终完成。换言之, lock-freedom 并不足于保证 lack of starvation.

Obstruction-freedom(无阻碍)

Obstruction-freedom 是这里讨论最弱的非阻塞担保特性。 一个方法被称为obstruction-free 是如果有一个时间点后它孤立执行 (别的线程没有步骤, 例如: 变成暂停), 它在有限的步骤完成。 所有 lock-free 对象是 obstruction-free 的, 但相反则不成立。

乐观并发控制(OCC)的方法通常是 obstruction-free。OCC的做法是每一位参与者都试着对共享对象执行操作,但如果一个参与者检测到与别人冲突, 则回滚修改,然后根据某种计划再次尝试。如果有一个时间点,这时只有一个参与者尝试,操作将成功。

推荐文献

  • 多核编程的艺术(The Art of Multiprocessor Programming), M. Herlihy and N Shavit, 2008. ISBN 978-0123705914
  • Java并发实战(Java Concurrency in Practice), B. Goetz, T. Peierls, J. Bloch, J. Bowbeer, D. Holmes and D. Lea, 2006. ISBN 978-0321349606