并发与异步处理
了解后端如何使用并发和异步处理模型高效地处理多个任务。
为什么并发在后端系统中很重要
现代后端系统必须同时处理大量传入请求;并发正是实现这一点的关键。
- 服务器会持续接收来自多个用户和系统的请求。
- 一次只处理一个请求会造成严重的延迟和瓶颈。
- 并发使系统能够在不必等待每个请求完全完成的情况下,同时推进多个请求的处理。
详情
在真实系统中,请求会持续不断地到达,而且通常是大批量到达,而不是一个接一个地来。一个后端服务每秒可能接收数百甚至数千个请求,每个请求都可能需要计算、数据库访问或外部 API 调用。
如果服务器严格按顺序处理这些请求,那么每个新请求都必须等待前一个请求完成。这会形成一个快速增长的队列,并导致用户无法接受的延迟。
这个问题会因为许多操作都涉及等待而变得更严重,例如网络调用或磁盘访问。在等待期间,CPU 处于空闲状态,这是一种低效的资源使用方式。
并发通过允许系统重叠执行工作来解决这个问题。当一个请求在等待 I/O 时,另一个请求就可以被处理。这样可以保持系统活跃,并提高整体吞吐量。
最终,后端系统能够在保持响应能力的同时同时服务大量用户,这也是现代应用的基本要求。
并发与并行
并发是关于管理多个正在进行的任务,而并行是关于同时执行多个任务。
- 并发允许任务通过在它们之间切换来推进。
- 并行使用多个 CPU 核心同时运行任务。
详情
并发关注的是系统如何组织和管理多个任务。服务器可能先开始处理一个请求,在等待 I/O 时暂停它,然后切换到另一个请求。任务轮流推进,即使资源有限,也能保持系统高效。
相比之下,并行依赖硬件。如果一台机器有多个 CPU 核心,它就可以在完全相同的时间执行多个任务。每个核心独立运行自己的任务,从而提高总处理能力。
关键区别在于,并发关注协调和调度,而并行关注同时执行。它们解决的是不同的问题,但通常会一起使用。
例如,后端服务器可能使用异步技术管理成千上万个并发请求,同时还将工作分配到多个 CPU 核心上,以实现并行执行。
理解这个区别对于分析系统性能、可扩展性,以及不同后端框架在负载下的运行方式非常重要。
线程
线程是进程中的一个执行单元,传统后端服务器使用多个线程并发处理请求。
每条通道都运行自己的执行路径。线程越多,同时进行的工作就越多——直到 CPU 或内存成为瓶颈。
- 每个线程表示服务器内部一个独立的执行流。
- 多个线程使服务器能够同时处理多个请求。
- 基于线程的模型被广泛用于像 Java Spring Boot 这样的后端框架中。
详情
进程是程序的一个运行实例,而在线程中,线程才是实际执行工作的单位。一个进程可以包含多个线程,每个线程独立执行,同时共享同一块内存空间。
在传统后端系统中,传入的请求会被分配给线程。例如,当一个请求到达时,服务器可能会从线程池中分配一个线程来处理它。该线程从开始到结束处理这个请求,包括业务逻辑、数据库查询以及响应生成。
这种模型很直接,也容易理解。每个请求都有自己的线程,因此执行流是隔离的,开发者可以用一种大体上顺序的方式编写代码。
不过,线程并不是免费的。每个线程都会消耗内存,并给系统带来额外开销。随着并发请求数量增加,创建过多线程可能会因为上下文切换和资源耗尽而导致性能下降。
由于这些限制,现代系统通常会将基于线程的方法与异步技术结合使用,但理解线程仍然很重要,因为许多生产系统仍然依赖这种模型。
阻塞与非阻塞操作
阻塞操作会让线程一直等待,直到任务完成;而非阻塞操作则允许线程继续执行其他工作。
当任务等待 I/O 时,阻塞模型会让工作线程停住。 非阻塞系统在等待时仍保持该通道活跃。
- 阻塞操作会暂停执行,直到结果准备好。
- 非阻塞操作允许系统继续处理其他任务。
详情
在阻塞操作中,线程启动一个任务,然后一直等待,直到该任务完全完成后才继续执行。例如,当线程发送数据库查询时,它可能会处于空闲状态,直到数据库返回响应。在这段时间里,线程无法执行任何其他有用的工作。
这在处理大量请求的系统中会成为一个主要限制。如果每个线程都花费大量时间等待 I/O,服务器就需要更多线程来维持吞吐量,这会增加资源使用和开销。
非阻塞操作采用不同的方法。线程不会等待,而是启动任务后立即继续执行其他工作。当结果准备好时,系统会收到通知,原始任务可以继续恢复。
这使得单个线程能够高效管理多个任务,尤其适用于 I/O 密集型工作负载,因为在这类场景中,等待时间往往占据了大部分执行时间。
理解阻塞和非阻塞行为之间的区别非常重要,因为它会直接影响后端系统在性能、响应性和可扩展性方面的设计。
异步 I/O
异步 I/O 允许服务器在等待数据库查询或网络调用等耗时操作时,继续处理其他任务。
- 许多后端操作都涉及等待外部系统,例如数据库或 API。
- 异步 I/O 可以防止线程在这些等待期间空闲。
- 这种方法可以提高吞吐量和资源利用效率。
详情
在后端系统中,许多操作并不是 CPU 密集型,而是 I/O 密集型。像查询数据库、调用外部 API 或从磁盘读取数据这样的任务,通常比内存中的计算耗时得多。
如果这些操作以阻塞方式处理,线程在等待结果时会保持空闲,这会浪费系统资源,并限制服务器能够处理的请求数量。
异步 I/O 通过允许服务器发起一个操作,然后继续处理其他工作,而不是一直等待,从而解决了这个问题。系统会跟踪这个待完成的操作,并在结果可用后恢复处理。
这使得单个线程或少量线程能够高效地管理大量并发请求,因此异步 I/O 成为高性能后端系统中的核心技术。
事件循环模型
事件循环模型使用少量线程,通过异步处理任务来处理大量请求,而不是为每个请求分配一个线程。
每个请求占用自己的工作线程。
一个循环轮转处理多个任务。
事件循环将重型工作分派给线程。
- 单个事件循环持续从队列中处理任务。
- 非阻塞操作使系统能够避免等待。
- 当异步操作完成时,会执行回调。
详情
在事件循环模型中,系统不会为每个请求分配单独的线程。相反,中央循环会持续检查新任务,并一次执行一个。当请求到达时,事件循环开始处理它,并以非阻塞方式发起任何所需的 I/O 操作。
事件循环不会等待这些操作完成,而是继续处理其他传入请求。这使系统保持忙碌,并避免在线程模型中常见的空闲等待时间浪费。
当异步操作完成时,其结果会被放入回调队列。事件循环最终会取出这个回调,并完成该请求剩余的工作。
这种模型的一个著名例子是 Node.js。它使用单线程事件循环结合 async I/O 来高效处理成千上万的并发连接,依赖 callbacks 和 promises 来管理执行流程。
这种方法对于 I/O 密集型工作负载非常高效,但需要仔细设计以避免阻塞事件循环,因为单个缓慢任务可能会延迟所有其他任务。
竞态条件
当多个线程同时访问并修改共享数据时,就会发生竞态条件,从而导致不可预测且错误的结果。
- 当多个线程在没有适当协调的情况下操作共享数据时,就会发生竞态条件。
- 最终结果取决于执行的时机和顺序。
- 它们是并发系统中 bug 的主要来源之一。
详情
当两个或多个线程同时读取并更新同一份数据时,就会产生竞态条件。由于这些操作没有同步,结果取决于哪个线程先执行,这使得系统行为变得不可预测。
例如,如果两个线程读取同一个账户余额,并且都尝试更新它,那么一个更新可能会覆盖另一个更新。这样即使每个单独的操作看起来都是正确的,最终数据仍然会出错。
这类问题很难检测,因为它们可能不会稳定地发生。很小的时序差异就可能改变执行顺序,从而引发难以复现和调试的 bug。
为了防止竞态条件,系统会使用锁、原子操作和数据库事务等同步技术。这些机制可以确保同一时间只有一个线程能够修改共享数据,或者确保操作在没有干扰的情况下安全执行。
问题部分
1 / 5
此课程属于高级内容
升级到高级版以去除模糊效果并解锁完整内容。