多核cpu是并发还是并行_多核CPU及其带来的并发更改

摩尔定律-戈登·摩尔在1965年的预测,即每个集成电路组件的数量将每18到24个月翻一番-举行了真,并预计将保持真,直到2015 - 2020年(参见相关主题 )。 直到2005年,CPU时钟速率也一直持续提高,这本身足以提高在这些CPU上执行的所有应用程序的性能。 应用程序开发社区在性能改进方面一路顺风,而很少或根本没有投资于算法改进。

但是,自2005年以来,时钟速率的增长和晶体管数量的增长一直在分化。 由于处理器材料的物理性质,时钟速率停止增加(甚至下降),并且处理器制造商开始将更多的执行单元(内核)打包到一个芯片(插槽)中。 这种趋势-在可预见的未来似乎可能会持续-从两个广义上讲,已经开始对应用程序开发和编程语言开发社区构成向上的压力:

  • 仅仅升级到功能更强大的CPU不再导致2005年前单线程应用程序的性能提高。 无论CPU中有多少个内核,单线程应用程序都执行相同的操作。 也就是说,无论CPU具有多少个内核,每个内核的吞吐量大致相同(假定在编译器,虚拟机或操作系统级别的自动并行化技术中没有突破)。
  • 升级到多核CPU将仅有益于系统上的增量负载,而不是现有负载。

有效利用可用CPU内核的唯一方法是通过并行性。 到目前为止,操作系统主要在进程级别使用并行性,以提供无缝的多任务,多处理体验。 在应用程序开发方面,基于线程的并发编程是实现并行性的主要机制。

基于线程的并行性具有以下优点:

  • 这是一个完善的编程模型。
  • 应用程序开发社区对如何创建,安排,执行和管理线程有深入的了解。
  • 开发人员受过训练,可以依次思考算法开发。 线程模型只是为并行性扩展了相同的方法。

但是,基于线程的应用程序并行性的问题胜于其优点。 本文介绍了一些原因,为什么显式基于线程的应用程序并行性可能不是利用CPU内核的最佳方法,以及为什么我们需要不同的编程范例。

调用栈深度

调用堆栈是OS或虚拟机维护的内部结构,用于处理所有方法调用。 线程执行中的每个方法调用都推入一个堆栈帧(由有关当前方法调用的详细信息组成,例如参数,返回地址和局部变量)。

图1显示了方法调用的内部:

图1.调用堆栈的内部结构和增长
多核cpu是并发还是并行_多核CPU及其带来的并发更改_第1张图片

无论您如何将应用程序模块化为多个逻辑层(例如控制器层,外观层,组件层和数据访问对象[DAO]层),线程都是运行时的最终编织者,并且只有一个堆栈。 调用堆栈是一个很棒的发明,用于在运行时处理源代码模块化。 但是随着应用程序复杂性的增加和系统负载的增加,当前的调用堆栈结构模型限制了应用程序的可伸缩性,并且它具有与内存大小和对象可访问性有关的内在问题。

对象可达性

深度调用堆栈的另一个问题是对象引用可以保留在调用堆栈中,但从不使用。 例如,在图1中 ,当线程正在执行执行流中最深层的方法时,不太可能需要调用堆栈中所有方法的所有局部变量和参数。 (例如,当线程执行DAO层代码时,应用程序不太可能需要servlet层,控制器层,立面层和其他层方法推送的调用堆栈中的所有局部参数和变量。电话)。 但是,它不会被释放或垃圾回收,因为它包含实时引用。

Java™调用栈实现旨在在方法调用返回时自动释放其所有引用。 当JVM不在高负载下时,这可能是可以接受的。 但是,当JVM使用大量活动线程运行时,这可能是个问题。 例如,如果每个线程在调用堆栈中最多保留5MB的未使用实时引用,并且有100个线程处于活动状态,则JVM将无法进行垃圾回收500MB的堆空间,因为调用堆栈变量和参数仍在对其进行引用。 在32位计算机上,这至少相当于该JVM的所有可用内存的25%,这是相当大的。

共享对象

基于线程的并行性的另一个关键问题是同步工作,这是由多个线程共享的对象的可变性引起的,如图2所示:

图2.共享内存
多核cpu是并发还是并行_多核CPU及其带来的并发更改_第2张图片

尽管同步的概念并不是什么新鲜事物,并且已被广泛采用,但它会损害应用程序的性能,因为锁定获取序列可能会强制线程等待或进入释放状态,从而在内部触发线程上下文切换。 上下文切换通常会减慢线程执行速度。 此外,它会清除内核中的所有管道指令和缓存。 在具有大量并行线程的JVM中,同步可能会由于同步和锁定而导致频繁的线程上下文切换。

顺序编程

顺序编程不一定是线程本身的问题,但它与应用程序使用线程的方式有关。 OS进程的逻辑概念是在计算的早期提出的,用于按顺序执行指令(在用户提交的作业中)。 但是,尽管此后某些过程的复杂性已经增加了许多倍,但是顺序编程的思维方式仍然占主导地位。 随着复杂性的增加,各种系统层(后端,中间层,前端)已经出现。 但是在一个层中,应用程序用例仍然是通过单个线程以顺序方式执行的,作为跨各种组件的所有逻辑的编织器。

您可以将其与亨利·福特引入装配线之前的制造过程进行比较。 然后,一个工人或一组工人将创建整个产品。 装配线使工人能够在整个制造过程中专注于特定的子任务。 通过节省工人在产品制造阶段花费的时间,它可以将生产率提高很多倍。

现代类似于装配线的是快餐店的客户订单处理。 预定数量的工作人员(每个人专门负责一组子任务)处理订单,每个工作人员仅完成全部工作的一部分。 完成该人的工作后,将半成品交给链中的下一个工人,依此类推,直到最终产品完成为止。 相反,考虑一个系统,其中每个员工从头到尾一次处理一个客户。 两者都是执行订单的有效方法,但是快餐系统的生产率更高。 一个处理整个订单的工人将花费太多时间在一个地方移动,而不是实际生产产品。 工人之间的移动会产生其他问题,例如空间争用和时间延迟。

现在想一想现代JEE应用服务器执行用户请求的方式。 它为单个用户请求分配一个专用线程。 如图3所示,该线程执行所有指令,从日志记录,数据库交互,Web服务调用,网络交互和逻辑计算等开始:

图3.线程流
多核cpu是并发还是并行_多核CPU及其带来的并发更改_第3张图片

无论源代码在控制器,模型,视图,外观和其他层方面的模块化程度如何,它都由单个线程执行。 这种类型的执行会在内部创建许多硬件资源争用,例如上下文切换。

结论

多线程是一种有效利用基础CPU资源的极好的方法。 但是随着系统的发展,开发和OS社区也将多线程的使用扩展到了应用程序级并行性。 应用程序开发社区开始使用基于线程的编程来按顺序执行所有应用程序逻辑。 自从多核CPU开始普及以来,随着核的数量逐渐增加,基于顺序的显式基于线程的编程效率越来越低。

在多核硬件上运行的可伸缩,高性能应用程序需要一种并行方法,该方法将应用程序逻辑分解为多个相互依赖的工作单元的切片,并将它们透明地链接在一起(而不是将它们与单个线程明确地绑定在一起),以便每个单独的工作单元都可以有效执行。

正如装配线彻底改变了制造Craft.io并提高了每一层的效率一样,正确的未来编程模型将改变我们设计应用程序软件的方式。 一种这样的抽象模型,即基于actor的编程(请参阅参考资料 ),将整个应用程序划分为多个片,以便可以将基础核心分配给这些片并以有效的方式并行执行。

免责声明

本文中的所有观点仅是我的,不一定是我雇主的观点。

致谢

我要感谢我的同事耶稣·贝洛和奥尔加·拉斯金的宝贵建议。


翻译自: https://www.ibm.com/developerworks/java/library/j-nothreads/index.html

你可能感兴趣的:(python,java,多线程,linux,大数据)