mysql中的同步 Synchronization

25 阅读9分钟

我们来深入探讨 MySQL 中关键的**同步(Synchronization)**机制,重点解析 native_mutex_tnative_rw_lock_tnative_cond_t 这三个核心原生同步对象。它们是 MySQL 实现高效、线程安全并发操作的基础。

为什么需要同步?

MySQL 是一个高度并发的服务器:

  1. 多客户端连接: 同时处理成百上千个客户端连接请求。
  2. 后台线程: 运行着多种后台线程(主线程、IO 线程、日志刷新线程、信号处理线程、复制线程、事件调度线程等)。
  3. 共享资源: 这些线程会并发访问共享内存数据结构,例如:
    • 全局变量(如配置参数 read_only, key_buffer_size)。
    • 查询缓存(如果启用)。
    • 表定义缓存(table cache)。
    • 用户连接列表。
    • 存储引擎内部结构(如 InnoDB 的缓冲池管理结构、锁表、事务系统)。
    • 线程列表。
    • 文件句柄管理。
    • 性能模式(performance_schema)数据。
  4. 并发风险: 不加控制的并发访问会导致数据竞争(Data Race)死锁(Deadlock)数据不一致(Data Inconsistency)程序崩溃(Crash)

解决方案:同步原语(Synchronization Primitives)

MySQL 使用底层操作系统提供的同步机制,并将其封装成统一的接口 native_mutex_tnative_rw_lock_tnative_cond_t。这些封装层的主要目的是:

  1. 跨平台抽象: 屏蔽 Windows(如 CRITICAL_SECTION, SRWLOCK, CONDITION_VARIABLE)和 Unix-like 系统(如 pthread_mutex_t, pthread_rwlock_t, pthread_cond_t)API 的差异。
  2. 性能优化: 根据平台选择最优实现(如 Windows Vista+ 的轻量级读写锁 SRWLOCK 优于 CRITICAL_SECTION 模拟的读写锁)。
  3. 统一接口: 提供一致的、易于在 MySQL 内部使用的 API。
  4. 性能模式(Performance Schema)集成: 便于监控锁的竞争情况。
  5. 错误处理: 封装底层错误码转换。

核心同步对象详解:

  1. native_mutex_t (原生互斥锁)

    • 目的: 提供最简单的互斥(Mutual Exclusion)访问。用于保护临界区(Critical Section),确保同一时刻只有一个线程可以进入该区域访问共享资源。
    • 特性:
      • 排他性: 一次只能被一个线程持有(加锁)。
      • 所有权: 哪个线程加的锁,通常必须由该线程解锁(防止其他线程误解锁)。
      • 阻塞: 如果一个线程试图获取(加锁)一个已被其他线程持有的互斥锁,该线程将阻塞(Block),直到锁被释放(解锁)。
      • 轻量级: 相对于其他锁,开销通常较小。
    • 典型应用场景:
      • 保护对单个全局变量的访问(例如计数器、标志位)。
      • 保护对小型共享数据结构的原子更新(例如链表头指针的插入/删除)。
      • 保护对文件描述符池的分配/释放。
      • InnoDB 内部大量使用(例如保护内存结构、mutex 监控计数器等)。
    • 核心操作 (伪代码/概念):
      • native_mutex_init(&mutex, nullptr): 初始化互斥锁。
      • native_mutex_lock(&mutex): 获取锁。如果锁已被占用,则阻塞等待。
      • native_mutex_trylock(&mutex): 尝试获取锁。如果锁未被占用,则获取并返回成功;如果已被占用,则立即返回失败(不阻塞)。
      • native_mutex_unlock(&mutex): 释放锁,允许其他等待的线程获取。
      • native_mutex_destroy(&mutex): 销毁互斥锁,释放相关资源。
  2. native_rw_lock_t (原生读写锁)

    • 目的: 解决互斥锁在读多写少场景下可能存在的性能瓶颈。允许多个读取者(Reader)同时访问共享资源,但写入者(Writer)需要独占访问。
    • 特性:
      • 两种锁模式:
        • 共享锁(Shared Lock / Read Lock): 可以被多个读取线程同时持有。持有共享锁时,其他线程仍然可以获取共享锁,但不能获取独占锁。
        • 独占锁(Exclusive Lock / Write Lock): 只能被一个写入线程持有。持有独占锁时,其他线程既不能获取共享锁也不能获取独占锁。
      • 优先级策略(通常):
        • 写入者优先: 如果写入者在等待,新的读取请求可能被阻塞,以避免写入者“饿死”(长时间无法获取锁)。这是常见的默认策略。
        • 读取者优先: 写入者可能“饿死”。MySQL 一般倾向于避免此策略。
      • 阻塞: 当一个线程请求它当前无法获取的锁模式时(如读取请求时已有写入者持有锁,或写入请求时已有任何持有者),该线程会阻塞。
    • 典型应用场景:
      • 保护频繁读取但很少修改的全局配置(如 key_cache->param)。
      • 保护全局系统变量(访问需要读锁,修改需要写锁)。
      • 保护查询缓存结构(查找用读锁,失效/插入用写锁)。
      • 保护表定义缓存(table cache)。
      • InnoDB 自适应哈希索引(AHI)的访问控制。
    • 核心操作 (伪代码/概念):
      • native_rw_lock_init(&rwlock): 初始化读写锁。
      • native_rw_rdlock(&rwlock): 获取共享(读)锁。
      • native_rw_tryrdlock(&rwlock): 尝试获取共享锁(非阻塞)。
      • native_rw_wrlock(&rwlock): 获取独占(写)锁。
      • native_rw_trywrlock(&rwlock): 尝试获取独占锁(非阻塞)。
      • native_rw_unlock(&rwlock): 释放锁(无论是读锁还是写锁)。
      • native_rw_lock_destroy(&rwlock): 销毁读写锁。
  3. native_cond_t (原生条件变量)

    • 目的: 允许线程在某个条件(Condition)不满足时挂起(Wait),并在条件可能满足时被唤醒(Signal)。它总是与一个互斥锁(native_mutex_t)结合使用
    • 特性:
      • 协作同步: 用于线程间的协调和通知。
      • 不持有状态: 条件变量本身并不保存条件的状态;它只是一个通信机制。条件的状态通常由受互斥锁保护的共享变量来指示。
      • 原子等待: wait 操作会原子地(Atomically) 释放关联的互斥锁并阻塞线程。当线程被唤醒时,它会自动地(Atomically) 重新获取该互斥锁。
      • 两种唤醒模式:
        • native_cond_signal(&cond): 唤醒至少一个在该条件变量上等待的线程。
        • native_cond_broadcast(&cond): 唤醒所有在该条件变量上等待的线程。
    • 典型应用场景:
      • 生产者-消费者模型:
        • 生产者线程在队列满时等待 (cond_wait) 消费者发出空位通知 (cond_signal/cond_broadcast)。
        • 消费者线程在队列空时等待 (cond_wait) 生产者发出新数据通知 (cond_signal/cond_broadcast)。
        • 保护队列的访问通常使用 native_mutex_t
      • 工作线程池: 工作线程在没有任务时在条件变量上等待 (cond_wait),主线程或分发线程在有新任务时唤醒 (cond_signal) 一个或所有空闲线程。
      • 等待特定事件: 如等待后台 IO 操作完成、等待复制位置推进、等待资源变为可用等。
      • InnoDB 内部: 大量用于后台线程协调(如 IO 线程通知刷新线程、事务提交等待日志刷盘等)。
    • 核心操作 (伪代码/概念):
      • native_cond_init(&cond, nullptr): 初始化条件变量。
      • native_cond_wait(&cond, &mutex): 原子地 释放 mutex 并阻塞等待 cond 被通知。被唤醒后,原子地 重新获取 mutex 并返回。调用此函数前,必须已经持有 mutex
      • native_cond_timedwait(&cond, &mutex, abstimeout): 带超时的等待。
      • native_cond_signal(&cond): 唤醒一个等待该 cond 的线程。
      • native_cond_broadcast(&cond): 唤醒所有等待该 cond 的线程。
      • native_cond_destroy(&cond): 销毁条件变量。

协同工作:条件变量与互斥锁的模式

// 线程 A (等待条件)
native_mutex_lock(&mutex); // 1. 获取保护共享状态和条件变量的锁
while (!condition_is_true) { // 2. 检查条件 (必须在循环中检查!避免虚假唤醒)
    native_cond_wait(&cond, &mutex); // 3a. 条件不真:原子释放mutex并等待cond
                                     // 3b. 被唤醒:自动重新获取mutex
}
// 4. 条件为真,执行操作...
do_something();
native_mutex_unlock(&mutex); // 5. 释放锁

// 线程 B (改变条件并通知)
native_mutex_lock(&mutex); // 1. 获取锁
do_something_that_makes_condition_true(); // 2. 改变共享状态,使条件可能成立
condition_is_true = true; // 3. 设置条件状态
native_cond_signal(&cond); // (或 broadcast) 4. 通知等待线程
native_mutex_unlock(&mutex); // 5. 释放锁

关键点:

  • while (!condition) 必须使用循环检查条件,因为唤醒可能是虚假的(Spurious Wakeup)(操作系统可能无故唤醒线程),或者在唤醒后、重新获取锁之前,条件可能又被其他线程改变。
  • 原子性: native_cond_wait 释放锁和阻塞是原子的,避免了通知发生在等待开始前导致信号丢失的竞态条件。

总结:MySQL 同步的价值与选择

  1. native_mutex_t 最简单、开销最小。适用于保护访问时间非常短的临界区或更新频率很低的共享变量。是条件变量的基础组件。
  2. native_rw_lock_t 在读操作远多于写操作且临界区较大时,能显著提升并发性能。写操作较重(需排他)。
  3. native_cond_t + native_mutex_t 实现线程间的高效协作和等待通知机制。是构建更复杂同步结构(如线程池、队列、事件驱动)的基础。

性能考虑:

  • 锁粒度: 锁保护的范围要适中。过粗(保护太多)会降低并发度;过细(锁太多)会增加锁开销和管理复杂性。
  • 持有时间: 尽量缩短持有锁的时间。避免在持有锁时进行阻塞操作(如文件IO、网络IO)。
  • 锁竞争: 使用性能模式监控热点锁的竞争情况,优化热点锁(如拆分锁、使用读写锁)。

结论:

native_mutex_tnative_rw_lock_tnative_cond_t 是 MySQL 实现高并发、线程安全的核心基础设施。它们提供了跨平台的互斥访问、读写分离的并发控制以及线程协作的能力。理解它们的工作原理、适用场景和配合方式(尤其是条件变量与互斥锁的模式)对于理解 MySQL 内部工作机制(存储引擎、连接管理、复制、后台任务等)至关重要,也是进行 MySQL 性能调优和故障诊断的基础知识。MySQL 通过封装这些原生对象,确保了核心代码在多平台上的正确性和高效性。