并发计算是计算机科学中的一种计算方式,其中多个计算活动在相互重叠的时间段内取得进展。并发系统包含多个组件,这些组件的执行不必遵循单一、预先确定的顺序。它们可以相互通信、共享资源并协调行动。并发并不要求同时执行:多个活动可以在一个处理器上交错执行,也可以在多个处理器上同时运行。并发计算的核心问题是如何组织独立执行的组件,以及如何保证它们之间的交互正确无误。(go.dev)
并发、并行与分布式计算
并发与并行计算描述的是计算中相互关联但不同的方面。并发关注如何围绕能够独立推进的活动来组织程序;并行则关注同时执行计算。并发程序可以在不并行执行的情况下运行,而当硬件资源充足且存在可独立完成的工作时,其结构也可能支持并行执行。增加并发组件并不会自动加快计算速度。(go.dev)
分布式计算涉及系统中相互通信的组件,通常通过不同计算机之间的消息传递来通信。这类系统具有并发性,因为不同组件中的事件可能不存在统一的执行顺序。莱斯利·兰波特在其 1978 年关于事件排序的论文中,形式化定义了“先发生于”(happened-before)关系,由此定义了一种偏序:某些事件通过本地执行或通信确定先后顺序,而另一些事件在这一关系下互不先于对方,因此被视为并发事件。(lamport.azurewebsites.net)
执行机制
并发活动可以用进程、线程或由运行时管理的任务来表示。程序中的线程可以操作共享对象。操作系统或运行时可以通过多个处理器、在单个处理器上分配时间片,或结合这两种机制来支持线程执行。因此,活跃线程的数量不一定等于同时执行的计算数量。(docs.oracle.com)
异步编程提供了另一种组织时间上重叠的工作的方法。在事件循环中,任务可以在等待某项操作时挂起,并在操作结果可用时恢复执行。例如,Python 的 asyncio 使用协作式调度:事件循环一次只运行一个任务,但当某个任务等待一个 future 对象时,可以运行其他任务或回调。因此,协程能够支持并发,而不一定引入并行执行。(docs.python.org)
执行机制与应用程序的结构是两个不同层面。服务器可以围绕传入的请求组织工作,而计算流水线则围绕连续的处理阶段组织工作。Go 的官方文档既介绍了独立执行的组件,也展示了基于通道的请求处理,说明通信如何表达并发活动之间的关系。(go.dev)
通信与同步
两种重要的交互方式是共享内存和消息传递。在共享内存方式下,活动通过读取和修改共同的数据来通信;在消息传递方式下,活动通过通道等通信机制交换值。这两种方式可以共存:Go 提供通道,也支持通过共享内存进行协调,其文档还明确指出了适合使用互斥锁的场景。(go.dev)
同步(计算机科学)用于约束执行顺序或资源访问。互斥锁提供互斥机制,使参与活动中一次只有一个能够执行受保护的临界区。信号量管理许可,而屏障则在共同的执行点协调各项活动。更高层的设施包括并发集合、执行器和 future 对象,分别用于组织共享访问、任务执行和结果处理。(docs.oracle.com)
同步也决定了内存可见性。内存模型规定一次读取可能观察到哪些写入,以及特定操作提供哪些顺序保证。因此,要保证正确性,仅仅防止表面上同时发生的更新还不够:程序还必须在写入与后续读取之间建立必要的关系。Java 的内存模型明确规定了多线程执行中的这类约束。(docs.oracle.com)
正确性与失效模式
当正确性取决于不受控制的操作顺序时,就会出现竞态条件。例如,一次递增操作可能包含读取计数器、加一和存储结果三个步骤。如果两个活动在任何一方存储更新结果之前都读取了同一个值,那么一次更新就可能覆盖另一次更新。因此,看似简单的表达式不一定是不可分割的操作。(docs.oracle.com)
数据竞争是一种更具体的问题,涉及未通过同步充分确定顺序的冲突内存访问。避免数据竞争并不能解决所有协调问题:一系列分别受到保护的操作,仍可能无法维持应用层面的要求。因此,评估并发算法时,必须依据其完整的规约,而不能只考虑单次访问的安全性。(docs.oracle.com)
无法取得进展的情况包括:死锁,即活动因无法解除的等待依赖关系而不能继续执行;饥饿,即某个活动反复无法获得所需资源;以及活锁,即活动持续相互响应,却没有完成有用的工作。饥饿和活锁不同于简单的停滞,因为其他执行仍可能继续,而受影响的工作却毫无进展。(docs.oracle.com)
规约与验证
正确性性质通常分为安全性和活性。安全性排除不可接受的行为,例如两个活动同时进入一个要求互斥的区域。活性要求最终取得进展,例如程序最终终止,或请求最终得到处理。仅有互斥并不能保证进展:如果一个系统中的所有活动都始终无法进入各自的临界区,它虽然满足互斥要求,却可能什么也完成不了。(lamport.azurewebsites.net)
形式验证可以将并发系统表示为状态机,并检查其可能的行为是否满足指定的性质。验证还可能需要公平性假设,用来规定哪些具备执行条件的活动最终会得到执行。TLA+ 等语言以数学形式表达系统行为和性质,从而能够明确考察实现之间的关系、不变式以及进展要求。(lamport.azurewebsites.net)
参考来源
- Concurrency is not parallelism - The Go Programming Languagego.dev
- Threads and Locksdocs.oracle.com
- Effective Go - The Go Programming Languagego.dev
- Time, Clocks, and the Ordering of Events in a Distributed Systemlamport.azurewebsites.net
- Coroutines and tasks — Python documentationdocs.python.org
- Effective Go - The Go Programming Languagego.dev
- java.util.concurrent (Java SE 25 & JDK 25)docs.oracle.com
- Thread Interferencedocs.oracle.com
- Starvation and Livelockdocs.oracle.com
- PlusCal Tutorial - Session 9lamport.azurewebsites.net
- A High-Level View of TLA+lamport.azurewebsites.net