MySQL/InnoDB源代码:线程并发访问控制(再续)

这是该系列的第三篇文章(12)了。之所以选择并发线程控制着手研究InnoDB的代码有两个原因:第一,这段代码相对独立,不要了解太多的相关代码就可以理解;第二,稍微多看一些代码你会发现,到处都是线程并发控制相关的代码出现,所以这也是一个基础。

第一篇中,介绍了InnoDB内部排他锁的实现,第二篇则介绍InnoDB内部读写锁的实现原理(这里说的“内部”是为了区别于数据库层面的读写锁)。本篇则将延续第二篇,介绍读写锁相关的代码实现。

1. 基本数据结构rw_lock_struct

InnoDB读写锁核心的数据结构是rw_lock_struct,下面列出了主要的成员:

struct rw_lock_struct { volatile lint lock_word; //标记该读写锁的状态(参考) volatile ulint waiters; //当前是否有等待获取锁的线程 volatile ibool recursive; //标记该锁是否被递归获取 volatile os_thread_id_t writer_thread; //如果有被以写锁方式获取,则记录该进程ID(为了递归获取) #ifndef INNODB_RW_LOCKS_USE_ATOMICS mutex_t mutex; //用来保护lock_word和该结构的 #endif ......

如果理解了前面介绍的读写锁的原理再来看rw_lock_struct的结构,还是比较简单的。相关的初始化和释放函数分别为rw_lock_create_func和rw_lock_free,也都比较简单。

2. 读写锁之读锁

读锁即以共享的方式获取锁。底层实现很简单:先查看lock_word的值获取当前锁的状态,如果当前线程可以获取读锁,则调用函数rw_lock_lock_word_decr以排他方式,更改lock_word的值,读锁即将lock_word减一。rw_lock_lock_word_decr的实现在本系列的第二篇中已经介绍了。如果检查lock_word发现当前进程还无法获取读锁(例如,某个线程以写锁方式获取了),则选择空循环一段(spin一段),然后再坚持lock_word。下面是一个读锁获取的示意图:

rw_lock_s_spin

3. 读写锁之写锁

较之读锁,写锁多了一个递归获取,即当前锁是以写方式被获取时,线程需要检查当前锁持有者的线程ID,如果是自己则可以递归获取写锁。

rw_lock_x_spin

在上面的示意图中,我们可以看到写锁获取时spin时,当spin了一定回合后线程会陷入等待,当其他的线程释放锁时则会广播一次消息,当前线程会被重新唤醒,然后继续spin。

4. 小结

这篇文章就贴了这么两张图…,发现写源代码相关的文章确实很难:如果文字太多了,那么就是介绍原理了;如果代码太多了,难免会枯燥,毕竟大段的代码拆开后就没有了顺畅的感觉,而且有堆页数之嫌(想想毕业论文吧)。这里尝试了一下“流程图+部分代码”的办法,试试。

两张示意图都是放在Flickr上的,功夫网对她很不友好,所以看到原图可能需要翻墙。另外,本网站也存了一份额外的拷贝:图1图2

广告时间:工作机会–MySQL Hacker

One response to “MySQL/InnoDB源代码:线程并发访问控制(再续)”

  1. 隆勇

    您好。您帖子里提到的错误,我们也频繁发生。日志里有 InnoDB: Warning: a long semaphore wait:
    –Thread 1512384832 has waited at row0row.c line 700 for 676.00 seconds the semaphore: 请问您最后定位到问题的原因没?是否可以分享一下解决方案。万谢!

Leave a Reply

Your email address will not be published. Required fields are marked *