今朝装饰,seo专员岗位要求,福步论坛,网站内页布局的不同可以使用不同的并发模型来实现并发系统。一并发模型指定的系统协作线程如何完成他们给予的任务。不同的并发模型以不同的方式拆分任务#xff0c;线程可以以不同的方式进行通信和协作。本并发模型教程将更深入地介绍撰写本文时#xff08;2015年至2019年#xff09;使用的最…可以使用不同的并发模型来实现并发系统。一并发模型指定的系统协作线程如何完成他们给予的任务。不同的并发模型以不同的方式拆分任务线程可以以不同的方式进行通信和协作。本并发模型教程将更深入地介绍撰写本文时2015年至2019年使用的最受欢迎的并发模型。 并发模型和分布式系统的相似性
本文中描述的并发模型类似于分布式系统中使用的不同体系结构。在并发系统中不同的线程彼此通信。在分布式系统中不同的进程相互通信可能在不同的计算机上。线程和进程本质上非常相似。这就是为什么不同的并发模型通常看起来与不同的分布式系统体系结构相似的原因。
当然分布式系统还面临着额外的挑战即网络可能会失败或者远程计算机或进程关闭等。但是如果CPU发生故障网卡发生故障磁盘发生故障则在大型服务器上运行的并发系统可能会遇到类似的问题。失败的可能性可能会更低但理论上仍会发生。
由于并发模型与分布式系统体系结构相似因此它们经常可以相互借鉴。例如用于在工作人员线程之间分配工作的模型通常类似于分布式系统中的负载平衡模型。错误处理技术例如日志记录故障转移任务的幂等也是如此。 共享状态与分离状态
并发模型的一个重要方面是组件和线程是设计为在线程之间共享状态还是具有独立的状态这些状态永远不会在线程之间共享。
共享状态意味着系统中的不同线程将在它们之间共享某些状态。通过状态是指一些数据通常是一个或多个对象或相似。当线程共享状态时可能会出现争用条件 和死锁等问题。当然这取决于线程如何使用和访问共享对象。 分开的状态意味着系统中的不同线程在它们之间不共享任何状态。万一不同的线程需要通信它们可以通过在它们之间交换不可变对象或通过在它们之间发送对象或数据的副本来进行通信。因此当没有两个线程写入同一对象数据/状态时可以避免大多数常见的并发问题。 使用单独的状态并发设计通常可以使代码的某些部分更易于实现和推理因为您知道只有一个线程将写入给定对象。您不必担心并发访问该对象。但是使用单独的状态并发性您可能需要更全面地考虑应用程序设计。我觉得这是值得的。我个人更喜欢单独的状态并发设计。 平行工人
第一个并发模型是我所说的并行工作器模型。传入的工作分配给不同的工人。这是说明并行工作程序并发模型的图 在并行工人并发模型中委托人将传入的作业分配给不同的工人。每个工人完成全部工作。这些工作程序并行工作在不同的线程中运行并可能在不同的CPU上运行。
如果在汽车制造厂实施并行工人模型则每辆汽车将由一名工人生产。工人将获得要制造的汽车的规格并会从头到尾制造所有东西。
并行工作程序并发模型是Java应用程序中最常用的并发模型尽管正在改变。java.util.concurrent Java包 中的许多并发实用程序都是设计用于此模型的。您还可以在Java Enterprise Edition应用程序服务器的设计中看到此模型的痕迹。 平行工人优势
并行工作程序并发模型的优点是易于理解。为了增加应用程序的并行化您只需添加更多工作程序即可。
例如如果您正在实施Web搜寻器则可以使用不同数量的工作程序来搜寻一定数量的页面并查看哪个数字提供了最短的总搜寻时间意味着最高的性能。由于Web爬网是一项IO密集型工作您最终可能会为计算机中的每个CPU /内核使用几个线程。每个CPU一个线程太少了因为它在等待数据下载时会处于许多空闲状态。 平行工人的劣势
但是并行工作程序并发模型具有一些隐藏在简单表面下的缺点。我将在以下各节中解释最明显的缺点。 共享状态会变得复杂
实际上并行工作程序并发模型比上面说明的要复杂一些。共享工作者经常需要访问内存或共享数据库中的某种共享数据。下图显示了如何使并行工作器并发模型复杂化 这种共享状态中的某些处于诸如工作队列之类的通信机制中。但是这种共享状态中的一些是业务数据数据缓存与数据库的连接池等。
一旦共享状态潜入并行工作程序并发模型中它就会开始变得复杂。线程需要以确保一个线程的更改对其他线程可见的方式访问共享数据推送到主内存中而不仅仅是卡在执行该线程的CPU的CPU缓存中。线程需要避免争用条件 死锁和许多其他共享状态并发问题。
此外当线程在访问共享数据结构时互相等待时并行化的一部分会丢失。许多并发数据结构正在阻塞这意味着一个或一组有限的线程可以在任何给定时间访问它们。这可能导致对这些共享数据结构的争用。高竞争本质上将导致访问共享数据结构的部分代码的执行序列化。
现代的非阻塞并发算法可以减少争用并提高性能但是很难实现非阻塞算法。
持久数据结构是另一种选择。永久数据结构在修改后始终保留其自身的先前版本。因此如果多个线程指向相同的持久数据结构并且一个线程对其进行了修改则修改线程将获得对新结构的引用。所有其他线程保留对旧结构的引用该旧结构仍保持不变因此是一致的。Scala编程包含几个持久数据结构。
虽然持久性数据结构是对共享数据结构进行并发修改的理想解决方案但持久性数据结构往往无法很好地执行。
例如一个持久列表会将所有新元素添加到列表的开头并返回对新添加元素的引用该引用随后指向列表的其余部分。所有其他线程仍保留对列表中先前第一个元素的引用并且对这些线程而言列表保持不变。他们看不到新添加的元素。
这样的持久列表被实现为链接列表。不幸的是链表在现代硬件上的表现不佳。列表中的每个元素都是一个单独的对象这些对象可以分布在整个计算机的内存中。现代CPU顺序访问数据的速度要快得多因此在现代硬件上从阵列顶部实现的列表中可以获得更高的性能。数组顺序存储数据。CPU高速缓存可以一次将更大的阵列块加载到高速缓存中并让CPU在加载后直接访问CPU高速缓存中的数据。对于链表将元素分散在整个RAM上这实际上是不可能的。 无国籍工人
共享状态可以由系统中的其他线程修改。因此工作人员必须在需要时重新读取该状态以确保该状态在最新副本上正常工作。无论共享状态是保留在内存中还是外部数据库中都是如此。不在内部保持状态但每次需要时都会重新读取状态的工作程序称为无状态。
每次需要时重新读取数据都会变慢。特别是如果状态存储在外部数据库中。 作业排序是不确定的
并行工作程序模型的另一个缺点是作业执行顺序是不确定的。无法保证首先执行或最后执行哪些作业。作业A可以在作业B之前提供给工人但作业B可以在作业A之前执行。
并行工作程序模型的不确定性使得很难在任何给定的时间点推断系统状态。这也使得很难如果不是不可能的话保证一项工作先于另一项工作发生。 流水线
第二种并发模型是我所说的组装线并发模型。我选择该名称只是为了适应早先的“并行工作者”隐喻。其他开发人员根据平台/社区使用其他名称例如反应系统或事件驱动的系统。这是说明组装线并发模型的图 工人的组织就像工厂中装配线的工人一样。每个工人仅完成全部工作的一部分。完成该部分后工人会将工作转发给下一个工人。
每个工作程序都在自己的线程中运行并且不与其他工作程序共享任何状态。有时也称为无共享并发模型。
使用组装线并发模型的系统通常设计为使用非阻塞IO。无阻塞IO意味着当工作人员开始IO操作例如从网络连接读取文件或数据时工作人员不会等待IO调用完成。IO操作很慢因此等待IO操作完成会浪费CPU时间。同时CPU可能正在做其他事情。IO操作完成后IO操作的结果例如读取的数据或写入的数据的状态将传递给另一个工作程序。
使用非阻塞IOIO操作将确定工作线程之间的边界。在必须启动IO操作之前工作人员将尽其所能。然后它放弃了对工作的控制。IO操作完成后装配线中的下一个工人将继续工作直到必须开始IO操作等为止。 实际上作业可能不会沿着一条装配线流动。由于大多数系统可以执行一项以上的工作因此工作会根据需要完成的工作在一个工人之间流动。实际上可能同时存在多个不同的虚拟装配线。这是现实中流水线系统中的工作流的样子 甚至可以将作业转发给多个工人进行并行处理。例如可以将作业转发给作业执行者和作业记录器。此图说明了三个装配线如何通过将其作业转发给同一工人中间装配线中的最后一个工人来完成 流水线甚至比这还要复杂。 反应性事件驱动系统
使用组装线并发模型的系统有时也称为反应式系统或 事件驱动系统。系统的工作人员会对系统中发生的事件做出反应这些事件是从外界接收到的或者是其他工作人员发出的。事件的示例可能是传入的HTTP请求或者某个文件已完成加载到内存等。
在撰写本文时有许多有趣的反应/事件驱动平台可用将来还会有更多。一些更受欢迎的似乎是 Vert.x 阿卡 Node.JSJavaScript
我个人认为Vert.x非常有趣特别是对于像我这样的Java / JVM恐龙。 演员与频道
角色和通道是装配线或反应/事件驱动模型的两个类似示例。
在演员模型中每个工人都称为演员。演员可以直接彼此发送消息。消息是异步发送和处理的。如前所述可以使用Actor来实现一个或多个作业处理装配线。这是说明参与者模型的图 在渠道模型中工作人员不直接相互通信。相反他们在不同的渠道上发布消息事件。然后其他工作人员可以在这些频道上收听消息而发件人不知道谁在收听。这是说明通道模型的图 在撰写本文时渠道模型对我来说似乎更灵活。工人不需要知道稍后在装配线中将处理什么工作的工人。它只需要知道将作业转发到哪个渠道或将消息发送到等等。频道上的侦听器可以订阅和取消订阅而不会影响工作人员对频道的写入。这允许工人之间的联轴器稍松一些。 流水线优势
与并行工作程序模型相比组装线并发模型具有多个优点。在以下各节中我将介绍最大的优点。 没有共享状态
工作人员与其他工作人员不共享任何状态的事实意味着无需考虑并发访问共享状态可能引起的所有并发问题就可以实现他们。这使实施工人变得容易得多。您将工作程序实现为好像是执行该工作的唯一线程-本质上是单线程实现。 有状态的工人
由于工作人员知道没有其他线程修改其数据因此工作人员可以是有状态的。有状态的意思是他们可以将需要操作的数据保留在内存中仅将更改写回最终的外部存储系统。因此有状态工人通常比无状态工人更快。 更好的硬件整合
单线程代码的优势在于它通常与底层硬件的工作方式更好地相符。首先当您可以假定代码以单线程模式执行时通常可以创建更多优化的数据结构和算法。
其次如上所述单线程有状态工作者可以在内存中缓存数据。当数据缓存在内存中时也更有可能将此数据也缓存在执行线程的CPU的CPU缓存中。这样可以更快地访问缓存的数据。
当以自然受益于底层硬件工作方式的方式编写代码时 我将其称为硬件一致性。一些开发商称这种机械同情。我更喜欢“硬件一致性”一词因为计算机几乎没有机械零件并且在这种情况下“同情”一词被用作“更好地匹配”的隐喻我相信“符合”一词可以很好地传达。无论如何这是挑剔的。使用您喜欢的任何术语。 可以订购工作
可以根据组装线并发模型以保证作业排序的方式实现并发系统。作业排序使在任何给定时间点推断系统状态变得更加容易。此外您可以将所有传入的作业写入日志。然后在系统的任何部分出现故障的情况下可以使用此日志从头开始重建系统状态。作业以特定顺序写入日志并且该顺序成为保证的作业顺序。这是这样的设计的外观 实施保证的工作订单不一定很容易但是通常是可能的。如果可以的话它可以极大地简化备份还原数据复制数据等任务因为所有这些都可以通过日志文件来完成。 组装线的缺点
组装流水线并发模型的主要缺点是作业的执行通常分散在多个工作人员中因此也分散在项目中的多个类中。因此很难确切地知道给定作业正在执行什么代码。
编写代码也可能会更困难。辅助代码有时被编写为回调处理程序。具有许多嵌套回调处理程序的代码可能会导致某些开发人员称之为回调地狱。回调地狱只是意味着很难跟踪所有回调中代码的实际作用以及确保每个回调都可以访问所需的数据。
使用并行工作程序并发模型这往往会更容易。您可以打开工作程序代码并从头到尾阅读几乎执行的代码。当然并行工作程序代码也可以分布在许多不同的类上但是执行顺序通常更容易从代码中读取。 功能并行
功能并行是第三种并发模型最近2015年被广泛讨论。
函数并行性的基本思想是使用函数调用实现程序。功能可以看作是相互发送消息的“代理”或“角色”就像在组装线并发模型AKA反应或事件驱动系统中一样。当一个函数调用另一个函数时这类似于发送消息。
传递给函数的所有参数都将被复制因此接收函数之外的任何实体都无法操纵数据。该复制对于避免共享数据出现争用情况至关重要。这使得函数执行类似于原子操作。每个函数调用都可以独立于任何其他函数调用执行。
当每个函数调用可以独立执行时每个函数调用可以在单独的CPU上执行。这就是说功能上实现的算法可以在多个CPU上并行执行。
使用Java 7我们获得了java.util.concurrent包含ForkAndJoinPool的软件包该软件包 可以帮助您实现类似于功能并行性的东西。使用Java 8我们获得了并行流 可以帮助您并行化大型集合的迭代。请记住有些开发人员对此表示批评ForkAndJoinPool您可以在本ForkAndJoinPool教程中找到批评的链接。
关于函数并行性的难点是知道要并行调用哪些函数。跨CPU协调函数调用会带来开销。一个功能完成的工作单元必须具有一定的大小才能负担此开销。如果函数调用很小则尝试并行化它们实际上可能比单线程单CPU执行慢。
根据我的理解一点都不完美您可以使用反应性事件驱动的模型来实现算法并实现类似于功能并行性的工作分解。使用均匀驱动的模型您可以更好地控制要并行化的对象和数量在我看来。
另外只有在该任务当前是程序唯一执行的任务时才有意义地将任务分配给多个CPU并产生开销。但是如果系统正在同时执行多个其他任务例如Web服务器数据库服务器和许多其他系统都在执行则尝试并行化单个任务毫无意义。无论如何计算机中的其他CPU都将忙于其他任务因此没有理由尝试以较慢的功能上并行的任务来打扰它们。组装流水线反应式并发模型可能会更好因为它具有较少的开销以单线程模式顺序执行并且与底层硬件的工作方式更好地兼容。 哪种并发模型最好
那么哪种并发模型更好
通常答案是这取决于系统应该执行的操作。如果您的工作自然是并行的独立的并且不需要共享状态则可以使用并行工作器模型来实现系统。
但是许多工作并非自然而然地平行和独立。对于这些类型的系统我相信组装线并发模型的优点要大于缺点比并行工作器模型要有更多的优点。
您甚至不必自己编写所有组装线基础结构的代码。像Vert.x这样的现代平台 已经为您实现了很多功能。我个人将为下一个项目探索在Vert.x等平台上运行的设计。我觉得Java EE不再具有优势。
原文地址Java并发-并发模型