深入理解“竞态条件”

文章通过一个收藏夹创建bug引出竞态条件问题:当多个操作(如写入和读取数据)并发执行时,结果取决于执行顺序。解决方案包括后端合并操作为原子操作,或前端采用乐观更新策略。核心启示是警惕异步操作风险,确保数据一致性。

作者头像
LumiBee
30 天前 · 46 1
分享

“竞态条件”:一个从刷新引起的 bug 说起

1. 一个难以解决的 bug

当我在测试新写好的创建收藏夹时,点击创建后,页面毫无变化;但只要我手动刷新,新建的收藏夹就会出现在目录中。

经过漫长的排查与翻阅资料,我终于明白这是由于**竞态条件(Race Condition)**所引起的一种“时灵时不灵”的不稳定状态。

2. 什么是竞态条件

可以用一个简单的比喻来帮助理解:

你给朋友寄了一个快递(操作A:写入数据),然后你立刻给他打电话问:“我的快递你收到了吗?”(操作B:读取数据)。

这里就存在一个“竞事”:是你的电话先到,还是快递先到?

  • 如果电话先到:朋友会告诉你:“没有啊,没收到。” 尽管你确实已经寄出了。
  • 如果快递先到:朋友会告诉你:“收到了,刚拿到!”

最终朋友给你的结果,完全取决于电话和快递这两个独立事件的执行顺序和时机

在软件世界里,竞态条件的定义是:当多个进程或线程并发地访问和操作同一个共享资源,而最终的结果取决于这些操作执行的相对时间顺序时,就发生了竞态条件。

它的核心要素有三个:

  1. 共享资源:多个操作都想访问和修改的同一个东西。在我们的例子中,这个资源就是数据库里的 favorites 表。
  2. 多个操作:至少有两个或以上的操作在几乎同一时间发生。在我们的例子中,这两个操作分别是:
    • 写操作:前端发送 POST 请求,要求后端在数据库中 INSERT 一条新的收藏夹记录。
    • 读操作:前端紧接着发送 GET 请求,要求后端从数据库中 SELECT 全部的收藏夹列表。
  3. 不可预知的时序:你无法保证哪个操作会先“完成”。网络延迟、服务器负载、数据库事务的执行时间等,都会给这个过程带来不确定性。

3. 如何解决竞态条件

解决竞态条件的核心思想在于:消除不确定性,确保操作的原子性/顺序性

方案一:后端主导

这应该是最可靠的方法

原理:将“创建”和“获取新列表”这两个操作在后端合并成一个原子操作。前端只发起一次请求,后端保证返回的数据是绝对正确的最终状态。

实现:修改创建收藏夹接口,让它在成功将新数据写入数据库并提交事务后立刻再次查询数据库,获取包含新成员的完整列表,并将其作为响应体返回给前端。

优点

  • 消除竞态:前端不再需要发起第二次 GET 请求,彻底消除了“读写竞争”的可能。
  • 提升性能:减少了一次网络往返。
  • 逻辑清晰:创建操作的最终结果由后端直接提供,前端只负责展示

方案二:乐观更新

这是一种提供最佳用户体验的前端技术

原理:UI界面“乐观地”相信后端操作一定会成功。

实现

  1. 用户点击“创建”后,JavaScript 不等待后端响应,而是立即在本地的数据列表里模拟一个新收藏夹(可以给一个临时ID),并马上刷新界面。此时用户会立刻看到新建的收藏夹。
  2. 同时,fetch 请求在后台发送。
  3. 若成功:后端返回成功响应(可能包含新收藏夹的正式ID和数据),前端再用真实数据替换掉之前的临时数据。整个过程用户无感知。
  4. 若失败:后端返回错误。前端需要实现“回滚”逻辑,比如弹窗提示“创建失败”,并将刚刚乐观添加的那个收藏夹从界面上移除。

优点:用户交互感觉极快,没有等待时间。

缺点:前端逻辑更复杂,需要处理状态同步和失败回滚。

4. 问题总结

竞态条件是并发编程中的一个核心概念,它源于对共享资源的无序访问。通过解决这个“刷新才可见”的Bug,我们已经亲身体验并解决了一个典型的前后端竞态条件。

bug 带给我的启示:

  • 警惕异步操作:当连续执行多个依赖共享资源的异步操作时,要时刻思考它们的执行顺序是否能得到保证。
  • 信任单一数据源:尽量让后端作为唯一可信的数据源,由它来保证操作的原子性,并返回最终的、一致的状态。
阅读量: 46

评论区

登录后发表评论

正在加载评论...
相关阅读

Redis09_深入理解主从复制

# 主从复制 在最基础的Redis应用中,可能只有一个Redis实例,这种模式简单直接,但存在明显的风险:一旦实例宕机,所有依赖它的服务都会收到影响,并且数据可能丢失.为了解决这个问题,Red...

194
0

Spring Security实战-构建安全的Web应用

Spring Security 作为 Spring 生态系统中不可或缺的一员,提供了一套全面且可扩展的机制来处理身份验证和授权。本文将结合实际应用场景,深入剖析 Spring Security ...

138
0

一个草稿箱功能的实现

# **从零到一:一个“自动保存草稿”功能的实现** 在现代Web应用中,用户体验至上。没有什么比用户在精心编辑长篇内容后,因意外关闭浏览器或网络问题而丢失所有心血更令人沮丧的了(一位站友就...

142
1

Redis10_深入理解哨兵模式

# Redis哨兵 Sentinel(哨兵)只是Redis的一种运行模式,不提供读写服务,默认在26379端口上,依赖于Redis工作.当主从复制中的主节点出现故障时,Redis Sentin...

134
0

Markdown 中文文案排版指北

# 中文文案排版指北 统一中文文案、排版的相关用法,降低团队成员之间的沟通成本,增强网站气质。 本文章的内容来自 GitHub 项目:[chinese-copywriting-guideli...

103
2

Spring AI 指南:如何自主构造 ChatMemory

# Spring AI 指南:如何自主构造 ChatMemory 在构建基于大语言模型(LLM)的对话式应用时,上下文管理是决定对话质量和连贯性的核心。用户不会希望模型在每一轮对话时都“忘记”...

2
0