《七周七并发模型》第一章概述

简介:

并发编程虽不是新的概念,最近却逐渐热门起来。一些编程语言,如Erlang、Haskell、Go、Scala、Clojure,也因对并发编程提供了良好的支持,而受到广泛关注。

并发编程复兴的主要驱动力来自于所谓的“多核危机”。正如摩尔定律①所预言的那样,芯片性能仍在不断提高,但相比加快CPU的速度,计算机正在向多核化方向②发展。正如Herb Sutter所说,“免费午餐的时代已然终结”③。为了让代码运行得更快,单纯依靠更快的硬件已无法满足要求,我们需要利用多核,也就是发掘并行执行的潜力。

1.1 并发?还是并行?

本书的主题是“并发”,那么又为何涉及了“并行”呢?虽然两者有所关联又常被混淆,但并发和并行的含义却是不同的。

一字之差也是差。一个并发程序含有多个逻辑上的独立执行块④,它们可以独立地并行执行,也可以串行执行。

一个并行程序解决问题的速度往往比一个串行程序快得多,因为其可以同时执行整个任务的多个部分。并行程序可能有多个独立执行块,也可能仅有一个。

我们还可以从另一种角度来看待并发和并行之间的差异:并发是问题域中的概念——程序需要被设计成能够处理多个同时(或者几乎同时)发生的事件;而并行则是方法域中的概念——通过将问题中的多个部分并行执行,来加速解决问题。

引用Rob Pike的经典描述①:

并发是同一时间应对(dealing with)多件事情的能力;并行是同一时间动手做(doing)多件事情的能力。

那么这本书讲述的是并发还是并行?

我妻子是一位教师。与众多教师一样,她极其善于处理多个任务。她虽然每次只能做一件事,但可以并发地处理多个任务。比如,在听一位学生朗读的时候,她可以暂停学生的朗读,以维持课堂秩序,或者回答学生的问题。这是并发,但并不并行(因为仅有她一个人,某一时刻只能进行一件事)。

但如果还有一位助教,则她们中一位可以聆听朗读,而同时另一位可以回答问题。这种方式既是并发,也是并行。

假设班级设计了自己的贺卡并要批量制作。一种方法是让每位学生制作五枚贺卡。这种方法是并行,而(从整体看)不是并发,因为这个过程整体来说只有一个任务。

超越串行编程模型

并发和并行的共同点就是它们比传统的串行编程模型更优秀。 本书将同时涵盖并发和并行(学究可能会给这本书起名为“七周七并发模型和并行模型”, 不过那样的话本书的封面会变得很难看)。

并发和并行经常被混淆的一个原因是,传统的“线程与锁”模型并没有显式支持并行。如果要用线程与锁模型为多核进行开发,唯一的选择就是写一个并发的程序,让其并行地运行在多核上。

然而,并发程序通常是不确定的,它会随着事件时序的改变而给出不同的结果。对于一个真正的并发程序,不确定性是其与生俱来且伴随始终的属性。与之相反,并行程序可能是确定的——例如,要将数组中的每个数都加倍,一种做法是将数组分为两部分并把它们分别交给一个核处理,这种做法的运行结果是确定的。使用一门直接支持并行的编程语言可以写出并行程序,而不会引入不入不 确定性。

1.2 并行架构

人们通常认为并行等同于多核,但现代计算机在不同层次上都使用了并行技术。比如说,单 核的运行速度现今仍能每年不断提升的原因是:单核包含的晶体管数量,如同摩尔定律预测的那 样变得越来越多,而单核在位级和指令级两个层次上都能够并行地使用这些晶体管资源。

位级(bit-level)并行

为什么32位计算机比8位计算机运行速度更快?因为并行。对于两个32位数的加法,8位计算 机必须进行多次8位计算,而32位计算机可以一步完成,即并行地处理32位数的4字节。 计算机的发展经历了8位、16位、32位,现在正处于64位时代。然而由位升级带来的性能改 善是存在瓶颈的,这也正是短期内我们无法步入128位时代的原因。
指令级(instruction-level)并行

现代CPU的并行度很高,其中使用的技术包括流水线、乱序执行和猜测执行等。
程序员通常可以不关心处理器内部并行的细节,因为尽管处理器内部的并行度很高,但是经 过精心设计,从外部看上去所有处理都像是串行的。
而这种“看上去像串行”的设计逐渐变得不适用。处理器的设计者们为单核提升速度变得越 来越困难。进入多核时代,我们必须面对的情况是:无论是表面上还是实质上,指令都不再串行 执行了。我们将在2.2节的“内存可见性”部分展开讨论。
数据级(data)并行 数据级并行

(也称为“单指令多数据”,SIMD)架构,可以并行地在大量数据上施加同一操 作。这并不适合解决所有问题,但在适合的场景却可以大展身手。

图像处理就是一种适合进行数据级并行的场景。比如,为了增加图片亮度就需要增加每一个像 素的亮度。现代GPU(图形处理器)也因图像处理的特点而演化成了极其强大的数据并行处理器。
任务级(task-level)并行
终于来到了大家所默认的并行形式——多处理器。从程序员的角度来看,多处理器架构最明 显的分类特征是其内存模型(共享内存模型或分布式内存模型)。

对于共享内存的多处理器系统,每个处理器都能访问整个内存,处理器之间的通信主要通过 内存进行,如图1-1所示。

01g61INoQHaW
图1-1 共享内存的多处理器系统
对于分布式内存的多处理器系统,每个处理器都有自己的内存,处理器之间的通信主要通过 网络进行,如图1-2所示。

QQ截图20150313001539
图1-2 分布式内存的多处理器系统

通过内存通信比通过网络通信更简单更快速,所以用共享内存编程往往更容易。然而,当处 理器个数逐渐增多,共享内存就会遭遇性能瓶颈——此时不得不转向分布式内存。如果要开发一 个容错系统,就要使用多台计算机以规避硬件故障对系统的影响,此时也必须借助于分布式内存。

1.3 并发:不只是多核

使用并发的目的,不仅仅是为了让程序并行运行从而发挥多核的优势。若正确使用并发,程序还将获得以下优点:及时响应、高效、容错、简单。

并发的世界,并发的软件

世界是并发的,为了与其有效地交互,软件也应是并发的。

一部手机可以同时播放音乐、上网浏览、响应触屏动作。我们在IDE中输入代码时,IDE正在后台悄悄检查代码语法。一架飞机上的系统也同时兼顾了好几件事情:监控传感器、在仪表盘上显示信息、执行指令、操纵飞行装置调整飞行姿态。

并发是系统及时响应的关键。比如,当文件下载可以在后台进行时,用户就不必一直盯着鼠标沙漏而烦心了。再比如,Web服务器可以并发地处理多个连接请求,一个慢请求不会影响服务器对其他请求的响应。

分布式的世界,分布式的软件

有时,我们要解决地理分布型问题。软件在非同步运行的多台计算机上分布式地运行,其本质是并发。

此外,分布式软件还具有容错性。我们可以将服务器一半部署在欧洲,另一半部署在美国,这样如果一个区域停电就不会造成软件整体不可用。下面就介绍容错性1。

1. 作者在此处用到了两个词:”fault-tolerant”和”resilient”,中文都译为”容错性”,但两者略有区别。由于这种微小的区别不会影响对本书的理解,因此之后的译文不再区分两者,统一使用”容错性”以方便读者理解。——译者注

不可预测的世界,容错性强的软件

软件有bug,程序会崩溃。即使存在完美的没有bug的程序,运行程序的硬件也可能出现故障。

为了增强软件的容错性,并发代码的关键是独立性和故障检测。独立性是指一个故障不会影响到故障任务以外的其他任务。故障检测是指当一个任务失败时(原因可能是任务崩溃、失去响应或硬件故障),需要通知负责故障处理的其他任务来处理。

串行程序的容错性远不如并发程序。

复杂的世界,简单的软件

如果曾经花费数小时纠结在一个难以诊断的多线程bug上,那你可能很难接受这个结论,但在选对编程语言和工具的情况下,比起串行的等价解决方案,一个并发的解决方案会更简洁清晰。

在处理现实世界的并发问题时,这个结论可以得到印证。用串行方案解决一个并发问题往往需要付出额外的代价,而且解决方案会晦涩难懂。如果解决方案有着与问题类似的并发结构,就会简单许多:我们不需要创建一个复杂的线程来处理问题中的多个任务,只需要用多个简单的线程分别处理不同的任务即可。

1.4 七个模型

本书精心挑选了七个模型来介绍并发与并行。

线程与锁:线程与锁模型有很多众所周知的不足,但仍是其他模型的技术基础,也是很多并发软件开发的首选。

函数式编程:函数式编程日渐重要的原因之一,是其对并发编程和并行编程提供了良好的支持。函数式编程消除了可变状态,所以从根本上是线程安全的,而且易于并行执行。

Clojure之道——分离标识与状态:编程语言Clojure是一种指令式编程和函数式编程的混搭方案,在两种编程方式上取得了微妙的平衡来发挥两者的优势。

actor:actor模型是一种适用性很广的并发编程模型,适用于共享内存模型和分布式内存模型,也适合解决地理分布型问题,能提供强大的容错性。

通信顺序进程(Communicating Sequential Processes,CSP):表面上看,CSP模型与actor模型很相似,两者都基于消息传递。不过CSP模型侧重于传递信息的通道,而actor模型侧重于通道两端的实体,使用CSP模型的代码会带有明显不同的风格。

数据级并行:每个笔记本电脑里都藏着一台超级计算机——GPU。GPU利用了数据级并行,不仅可以快速进行图像处理,也可以用于更广阔的领域。如果要进行有限元分析、流体力学计算或其他的大量数字计算,GPU的性能将是不二选择。

Lambda架构:大数据时代的到来离不开并行——现在我们只需要增加计算资源,就能具有处理TB级数据的能力。Lambda架构综合了MapReduce和流式处理的特点,是一种可以处理多种大数据问题的架构。

相关实践学习
在云上部署ChatGLM2-6B大模型(GPU版)
ChatGLM2-6B是由智谱AI及清华KEG实验室于2023年6月发布的中英双语对话开源大模型。通过本实验,可以学习如何配置AIGC开发环境,如何部署ChatGLM2-6B大模型。
相关文章
|
6月前
|
人工智能 数据可视化 API
一文看懂 OpenClaw:基础概念详解 + 部署实操教程
2026年初爆火的开源AI智能体OpenClaw(昵称“龙虾”),突破传统聊天AI局限,具备读写文件、运行代码、操控浏览器等“动手能力”。支持微信/飞书交互,兼容多模型,本地或云端一键部署,赋能办公、开发、生活与创作场景。
1374 5
|
6月前
|
算法 调度 数据库
演化计算与抽样方法构造新算法流程:从 AlphaEvolve 看 LLM × EA 融合范式
本文系统解析AlphaEvolve——Google DeepMind提出的LLM×EA融合新范式:以语义引导的抽样机制、双模型协同进化(Gemini Flash+Pro)、自动评估闭环,实现算法的自主发现与优化,已突破矩阵乘法纪录并提升训练效率。(239字)
|
11月前
|
存储 缓存 安全
阿里云服务器通用算力型u2i实例详解:企业级性能保障,高性能算力
u2i实例是阿里云通用算力型(U实例)中的一种规格族,采用全新CIPU架构,兼容多代际服务器,并支持Intel最新的第五代和第六代至强平台。作为企业级入门独享实例,提供企业级的性能保证,满足客户对成本敏感的业务需求,主要场景包括 web、java、中小型数据库、离线业务、通用企业应用场景。以下是关于u2i实例的核心信息:
663 156
|
8月前
|
监控 搜索推荐 应用服务中间件
‌301重定向完全指南:从原理到生产级实践
301重定向是HTTP永久性跳转机制,用于旧URL迁移至新地址。核心价值:①SEO权重传递(90–99%);②保障用户体验,避免404;③统一URL规范(域名/协议/路径)。适用场景包括域名更换、HTTPS升级、结构优化等。Nginx、Apache及WordPress插件均可高效实现。(239字)
2754 2
|
人工智能 文字识别 安全
大模型能力评测方式很多?
AI评测非单一分数比拼,而是多维度、多方法的系统工程。其核心框架可拆解为基础维度、主流基准与关键方法,共同构成模型能力的“CT扫描”系统。
929 0
|
缓存 NoSQL Linux
在CentOS 7系统中彻底移除MongoDB数据库的步骤
以上步骤完成后,MongoDB应该会从您的CentOS 7系统中被彻底移除。在执行上述操作前,请确保已经备份好所有重要数据以防丢失。这些步骤操作需要一些基本的Linux系统管理知识,若您对某一步骤不是非常清楚,请先进行必要的学习或咨询专业人士。在执行系统级操作时,推荐在实施前创建系统快照或备份,以便在出现问题时能够恢复到原先的状态。
1434 79
|
10月前
|
消息中间件 Ubuntu 中间件
Linux网络中间件入门指南(从零开始搭建高效网络通信架构)
本教程来源:https://www.vps5.cn/带你入门Linux网络中间件,介绍其在分布式系统中的核心作用。通过Ubuntu部署RabbitMQ实例,学习消息队列、服务代理等常见类型的应用与选型建议,助你提升系统通信效率与可靠性。
|
JavaScript 前端开发 安全
【逆向】Python 调用 JS 代码实战:使用 pyexecjs 与 Node.js 无缝衔接
本文介绍了如何使用 Python 的轻量级库 `pyexecjs` 调用 JavaScript 代码,并结合 Node.js 实现完整的执行流程。内容涵盖环境搭建、基本使用、常见问题解决方案及爬虫逆向分析中的实战技巧,帮助开发者在 Python 中高效处理 JS 逻辑。
|
数据可视化 数据挖掘 BI
QuickBI全新高亮联动功能,引爆数据交互新体验!
本文介绍了高亮联动在BI系统中的应用,包括其定义、用途及实现方案。高亮联动通过用户在某个组件上的操作,自动在其他组件中高亮显示相关数据,帮助用户快速识别数据间的关联性。文中详细解释了筛选联动与高亮联动的区别,并展示了具体的实现步骤和图表结构。通过技术创新和用户体验优化,高亮联动功能将为用户提供更高效、直观的数据分析工具。
810 0
|
Java
Java Object 类详解
在 Java 中,`Object` 类是所有类的根类,每个 Java 类都直接或间接继承自 `Object`。作为所有类的超类,`Object` 定义了若干基本方法,如 `equals`、`hashCode`、`toString` 等,这些方法在所有对象中均可使用。通过重写这些方法,可以实现基于内容的比较、生成有意义的字符串表示以及确保哈希码的一致性。此外,`Object` 还提供了 `clone`、`getClass`、`notify`、`notifyAll` 和 `wait` 等方法,支持对象克隆、反射机制及线程同步。理解和重写这些方法有助于提升 Java 代码的可读性和可维护性。
775 20

热门文章

最新文章