第1章 并发控制概述

并发控制这玩意儿,说白了就是管好"多件事同时发生"的场景。你想想看,一个系统里多个线程、多个进程、甚至多台机器同时在干活,不出乱子才怪。我做了十几年分布式系统,见过太多因为并发没处理好而翻车的案例。

这一章我们先搭个框架。把并发场景、常见问题、控制目标、还有那些策略工具都过一遍。后面的章节再一个一个深入拆解。

1.1 并发场景——哪些地方需要操心

并发不是理论概念,它就藏在我们每天写的代码里。我个人习惯把并发场景分为三类:

  • 多线程编程:一个进程内多个线程共享堆内存。Java的Thread、Go的goroutine都算。最常见的战场。
  • 多进程协作:进程间通过共享文件、数据库、消息队列等方式交互。操作系统里的经典问题——生产者消费者,就是典型。
  • 分布式系统:多台机器通过网络通信,没有真正的共享内存。这儿的并发问题最难搞,因为"时间"和"顺序"都变得模糊了。

具体到业务场景,秒杀抢购、银行转账、库存扣减、分布式ID生成……这些你肯定不陌生。每一个场景背后都藏着并发的影子。

一个小观察:很多团队只在"出事了"才想起并发控制。其实在设计阶段就识别出并发场景,比事后打补丁省心十倍。

1.2 并发问题——四个最常见的坑

并发场景一旦失控,就会冒出各种各样的毛病。归纳下来,主要有四类。我按危害程度排了个序:

1.2.1 竞态条件

两个线程同时读写一个共享变量,最终结果取决于谁先谁后——这就是竞态。我在项目中遇到过最典型的案例:一个计数器同时在多个goroutine里递增,最后结果总比预期少。

看个例子:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var count = 0
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            count++ // 这里就是竞态条件
            wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println(count) // 几乎不会是1000
}

count++ 看着像一行操作,实际拆成了"读-改-写"三步。两个线程同时读到旧值,加完写回去,就覆盖了对方的更新。结果永远少。

1.2.2 死锁

两个线程各持有一把锁,都在等对方释放——谁也动不了。我曾经排查过一个线上服务卡死的问题,查了半天发现是A线程持有锁1等锁2,B线程持有锁2等锁1。经典的"循环等待"。

1.2.3 活锁

活锁比死锁更"温柔"——线程没阻塞,但一直在做无用功。比如两个线程检测到冲突后各自退让,结果又同时重试,反复横跳,就是干不了正事。嗯,这场景在分布式协调里挺常见的。

1.2.4 饥饿

优先级低的线程一直拿不到资源,永远无法执行。我见过一个调度系统,高优任务源源不断,低优任务等了几个小时才被调度——这其实就是饥饿。

注意:饥饿不像死锁那么"显眼",系统还在跑,但部分线程已经被"饿死"了。监控里往往发现不了,得靠业务延迟指标才能察觉。

这四个问题,我把它们的核心差异整理成了一张表:

问题类型 直接表现 资源状态 系统是否可恢复 典型触发条件
竞态条件 数据不一致 资源正常 可恢复(结果已错) 未加锁的共享写操作
死锁 线程完全卡住 锁被永久占用 需外部干预 循环等待锁
活锁 线程空转 资源来回切换 可能自行恢复 冲突重试策略不当
饥饿 部分线程无进展 资源被抢占 调整优先级后可恢复 不公平的调度策略

1.3 并发控制目标——我们要解决什么问题

搞清楚了问题,再看目标就清晰了。并发控制不是"为了控制而控制",它有明确的指向:

  • 数据一致性:无论多少线程同时操作,最终数据是正确、完整的。这是底线。
  • 系统可用性:并发不能把系统搞崩。死锁、饥饿都会导致服务不可用,必须避免。
  • 性能与公平:控制是有代价的。锁加多了,性能就掉了。我们要在"安全"和"快"之间找平衡,同时保证各线程有公平的机会。

这三个目标其实是互相制约的。一致性要求越严,性能损失越大;可用性要求高,可能就得放松一致性。你在做架构决策时,心里得有这个权衡。

1.4 常用策略总览——工具箱里有什么

针对上面那些问题,业界积累了一系列策略。我把它们分成四大类,也是本课程后续要逐一展开的内容:

  • 锁机制:互斥锁、读写锁、乐观锁、分布式锁。最经典也最直接的手段。
  • 原子操作:CAS、原子变量、无锁数据结构。比锁更轻量,但适用场景有限。
  • 事务机制:本地事务、分布式事务(2PC、TCC、Saga)。适合数据库层面的并发控制。
  • 限流降级:令牌桶、漏桶、熔断器。重在"保护"而非"协调",防止系统被突发流量冲垮。

下面这张图把整个知识体系串了起来——从场景到问题,从目标到策略,你一看就明白各模块之间的关系。

并发控制知识体系 并发场景 多线程编程 多进程协作 分布式系统 并发问题 竞态条件 死锁 活锁 饥饿 控制目标 数据一致性 系统可用性 性能与公平 常用策略 锁机制 原子操作 事务机制 限流降级 图:并发控制知识体系 — 从场景到问题,再到目标与策略的完整链路

看到没?从上到下是一条清晰的链路:哪些场景会出问题 → 具体出什么问题 → 我们要达成什么目标 → 用什么工具来解决。后续每一章,就是沿着这条链路逐个击破。

给新手的建议:别急着上策略。先学会"识别场景"和"诊断问题"。我见过太多团队,一上来就上分布式锁,结果发现根本不是锁的问题——其实是数据副本没对齐。

这一章算是"总览"。接下来的章节,我们会从竞态条件开始,一步步深入每个问题、每种策略的细节。你先把这张图刻在脑子里,后面学起来就会轻松很多。