I've noticed that all designs I have come across can be multi-threaded using the actor mode - separating each work module into a different actor and using a message queue (for me a .NET ConcurrentQueue) to pass messages. What other good multi threaded models exist?
5 回答
我认为,通信顺序进程是一种比参与者模型更好的并发模型。它解决了 Actor 模型(和其他模型)的许多问题,例如死锁、活锁、饥饿。看看这个,更实用的是这个。
主要区别如下。在 Actor 模型中,消息是异步发送的。但是在 CSP 中消息是同步发送的;在接收者准备好接收之前,发送者无法发送。
这一简单的限制使世界变得与众不同。如果你有一个不正确的设计,可能会出现死锁,那么在actor模型中它可能会发生也可能不会发生(它通常只在向老板演示时才会发生......)。然而,在 CSP 中,死锁总是会发生,让您毫无疑问地认为您的设计是不正确的。好的,所以你仍然需要修复它,但没关系;解决您知道存在的问题比尝试详尽地测试是否存在问题要容易得多(您在参与者模型中的唯一选择)。
CSP 的严格同步方法似乎会导致响应时间问题;例如,人们担心 GUI 线程无法继续前进,因为它无法向繁忙的工作线程发送消息,而该工作线程还没有达到“读取”的程度。您要做的是确保工作负载分布在足够多的线程上,以便它们都可以在可接受的时间内重新等待新消息。CSP 不会让您侥幸逃脱。演员模型确实如此,但不要被欺骗;你只是在制造未来的问题。
在 .NET 中,ConcurrentQueue 不是 CSP 的正确原语,除非您在顶部分层同步机制。我也在 TCP 套接字之上添加了严格的同步。事实上,我通常最终会编写某种库来抽象套接字和管道,因此对于“进程”(正如他们在 CSP 用语中所说的那样)是这台机器上的线程还是整个其他进程变得无关紧要在网络连接结束的另一台机器上。很好 - 从一开始就内置了可扩展性。
我已经用 CSP 方式做了 23 年了,我不会再用其他方式了。以这种方式构建了一些具有数千个线程的大型系统。
==编辑==
看来这个答案仍然引起了一些关注,所以我想我会添加它。对于 Windows 开发人员,Task Parallel Library 有DataFlow命名空间。它必须单独下载。微软如此描述它:“这种数据流模型通过为粗粒度数据流和流水线任务提供进程内消息传递来促进基于参与者的编程。” 出色的!它使用像BufferBlocks这样的类作为通信渠道。重要的是 BufferBlock 具有默认为 Unbounded 的BoundedCapacity属性,该属性适合 Actor 模型。将此值设置为 1,您现在已将其转换为 CSP 样式的通信通道。
最后补充一点,除了 CSP 之外,还有其他各种多线程模型。这个Wikipedia 页面列出了其他几个,例如CCS、ACP和LOTOS。阅读这些文章暗示了一个深邃而黑暗的洞穴,学者们在其中游荡,等待突袭一个流浪的软件开发人员。
问题在于,学术上的默默无闻通常意味着完全缺乏实用、可用级别的工具和库。将一个完善的、经过验证的学术研究转化为一组库和工具需要付出很多努力。对于更广泛的软件社区来说,几乎没有真正的动力去研究一篇理论论文并将其变为现实。
我喜欢 CSP,因为基于 select() 或 pselect() 实现自己的 CSP 库实际上非常简单。我已经做过好几次了(我必须学习代码重用),再加上肯特大学的好人为喜欢 Java 的人编写了 JCSP。我不建议在 Occam 中开发(尽管它仍然几乎是可能的);支持和可维护性将成为未来的问题。CSP 可能是最容易进入的一种,鉴于其良好的特性,它非常值得。
@杰里米弗里斯纳
未来的问题
为了扩展我所说的“未来问题”的含义,我指的是在异步系统中,消息的发送者不知道接收者是否实际上跟上了需求。发件人不知道,因为它所知道的只是某个消息缓冲区已接受该消息。然后,当接收者愿意接受它时,下面的传输(例如 tcp)继续将消息推送过来。
因此,在压力下,系统可能无法按要求执行,因为消息传输将不可避免地具有有限的能力来吸收接收者尚不能接受的消息。发件人只有在问题已经开始发展后才发现这一点,到那时再采取任何措施可能为时已晚。
测试当然可以揭示这个问题,但是您必须小心,测试确实已经耗尽了传输器吸收消息的能力。只是全速快速爆炸可能具有欺骗性。
当然,同步系统会产生开销(“你准备好了吗?”、“不,还没有”、“现在?”、“是的!”、“那么你来了”),这不会发生在异步系统。所以平均而言,异步系统会更高效,实际上可能具有更高的吞吐量等。这就是为什么世界上大多数系统实际上都是异步的,也是系统并不总是达到原始系统的全部容量的原因网络带宽/处理时间可能会建议。在我看来,当接近满负荷时,异步系统往往不会优雅地限制。
在我的问题中一直有幸拥有过多的带宽,出于成功确定性的原因,我选择了同步路由;我并没有在带宽上损失太多,但我损失了很多风险,这很好。
从同步转换为异步
也许吧,但它可能没有什么价值。在同步系统中,只有成功地平衡了线程之间的分工,它才能按要求工作。也就是说,有足够多的线程在执行慢速位,因此不会阻碍快速位。弄错了,系统肯定不够快。
但是这样做之后,您就拥有了一个系统,其中每个组件都能够毫无延迟地继续发送消息,因为它发送到的所有内容都已准备好并正在等待(因为您在平衡工作负载方面的技能和判断力)。因此,如果您确实转换为异步消息传输,那么您所做的就是在这些消息的传输中节省少量时间。您所做的更改不会导致工作负载得到更快的处理。但是,如果节省带宽是目标,那么它也许是值得的。
当然,进行这种平衡可能是一件困难的事情,并且处理 HDD 访问时间、网络等变量可能很难克服。我经常不得不实施“下一个可用”工作负载共享方案。但可以肯定的是,在像我和你一起玩的那些实时信号处理系统中,基本上是在处理像 OpenVPX 的 RapidIO 这样非常可靠的传输,你只是对数据进行求和(不处理数据库、磁盘等),并且数据速率非常高(如今 1GByte/sec 是完全可行的,事实上,我在 13 年前就处理过如此高的数据速率;那是我的工作)。严格同步意味着您要么绝对跟上数据速率,要么绝对不跟上。使用异步,它更像是...
适合所有人的实时操作系统!
拥有一个实时操作系统也是一个必不可少的组件,而现在它似乎是为 Linux 提供的 PREEMPT_RT 补丁集,它为许多业内人士完成了这项工作。Redhat 对它进行了预打包(RedHat MRG),但是对于来自 CERN 好心人的免费赠品 Scientific Linux 是好的和免费的!我强烈怀疑,如果使用 PREEMPT_RT,许多系统在接近容量限制的情况下会更顺畅地工作——它可以很好地解决问题。
并发是一个引人入胜的话题,有很多实现方法,基本问题是 - “我如何协调并行计算?” .
一些并发模型是:
期货
Futures也称为Promises或Tasks是充当异步计算结果的代理的对象。当计算实际需要该值时,线程会冻结,直到计算完成,从而实现同步。
Futures 是.NET和ES6的首选并发模型。
软件事务内存
软件事务内存 (STM)通过将操作分组到事务中来同步对共享内存的访问(很像锁)。任何单个事务只能看到共享内存的单个视图并且是原子的。这在概念上类似于有多少数据库处理并发。
STM 是Clojure和Haskell的首选并发模型。
演员模型
Actor 模型侧重于消息传递。Actor 接收到消息并可以决定发送消息作为响应,生成其他 Actor,进行本地更改等。这可能是这些模型中最不紧密耦合的模型,因为 Actor 仅交换消息而没有其他内容。
Actor 模型是Erlang和Rust的首选并发模型。
请注意,与上面提到的语言不同,大多数语言没有大炮或首选并发模型,即使是那些对一种模型表现出强烈偏好的语言通常也将其他模型实现为库。
我个人的观点是,Futures 在使用和推理的简单性方面优于 STM 和 Actors,但这些模型都没有本质上是“错误的”,我认为两者都没有缺点。您可以使用您喜欢的任何一个,而不会产生任何后果。
并行处理最通用的模型是Petri 网。它将计算表示为纯数据依赖图,表示最大并行度。所有其他模型都源于它。
数据流计算模型http://www.cs.colostate.edu/cameron/dataflow.html,http://en.wikipedia.org/wiki/Dataflow_programming几乎一样强大。它将 Petri 网的位置限制为只有一个输出弧。在实践中,这很有用,因为具有多个输出弧的地方很难实现,导致不确定性,并且很少需要。
Actor 模型是一种数据流模型,其中节点可能只有 2 条输入边——一条用于输入消息,一条用于 Actor 的状态。如果您想编写具有副作用和多个参数的函数,这是一个严重的限制。