MySQL系列:innodb源代码分析之线程并发同步机制
innodb是一个多线程并发的存储引擎,内部的读写都是用多线程来实现的,所以innodb内部实现了一个比較高效的并发同步机制。
innodb并没有直接使用系统提供的锁(latch)同步结构,而是对其进行自己的封装和实现优化。可是也兼容系统的锁。我们先看一段innodb内部的凝视(MySQL-3.23):
Semaphore operations in operating systems are slow: Solaris on a 1993 Sparc takes 3 microseconds (us) for a lock-unlock pair and Windows NT on a 1995 Pentium takes 20 microseconds for a lock-unlock pair. Therefore, we have toimplement our own efficient spin lock mutex. Future operating systems mayprovide efficient spin locks, but we cannot count on that.
大概意思是说1995年的时候。一个Windows NT的 lock-unlock所须要耗费20us,即使是在Solaris 下也须要3us,这也就是他为什么要实现自己定义latch的目的,在innodb中作者实现了系统latch的封装、自己定义mutex和自己定义rw_lock。以下我们来一一做分析。
1 系统的mutex和event
定义例如以下:
typedef pthread_mutex os_fast_mutex_t;
而os_event_t相对复杂,它是通过os_fast_mutex_t和一个pthread_cond_t来实现的,定义例如以下:
typedef struct os_event_struct{os_fast_mutex_t os_mutex;ibool is_set;pthread_cond_t cond_var;}os_event_t;
下面是os_event_t的两线程信号控制的样例流程:
2 CPU原子操作
在
asm volatile("movl $1, %%eax; xchgl (%%ecx), %%eax" :"=eax" (res), "=m" (*lw) :"ecx" (lw));
这段代码是什么意思呢?
事实上就是将lw的值设置成1,而且返回设置lw之前的值(res),这个过程都是CPU须要回写内存的,也就是CPU和内存是全然一致的。
除了上面设置1以外。另一个复位的实现,例如以下:
asm volatile("movl $0, %%eax; xchgl (%%ecx), %%eax" :"=m" (*lw) : "ecx" (lw) : "eax");
这两个函数交叉起来使用,就是gcc-4.1.2以后的__sync_lock_test_and_set的基本实现了。在MySQL-5.6的Innodb引擎其中,将以上汇编代码採用了__sync_lock_test_and_set取代。我们能够採用原子操作实现一个简单的mutex.
#define LOCK() while(__sync_lock_test_and_set(&lock, 1)){}
#define UNLOCK() __sync_lock_release(&lock)
以上就是一个主要的无锁结构的mutex,在linux下測试确实比pthread_mutex效率要高出不少。当然在innodb之中的mutex实现不会只这么简单,须要考虑的因素还是比較多的,比如:同线程多次lock、lock自旋的周期、死锁检測等。
3 mutex的实现
struct mutex_struct
{ulint lock_word; /*mutex原子控制变量*/os_fast_mutex_t os_fast_mutex; /*在编译器或者系统部支持原子操作的时候採用的系统os_mutex来替代mutex*/ulint waiters; /*是否有线程在等待锁*/UT_LIST_NODE_T(mutex_t) list; /*mutex list node*/os_thread_id_t thread_id; /*获得mutex的线程ID*/char* file_name; /*mutex lock操作的文件/ulint line; /*mutex lock操作的文件的行数*/ulint level; /*锁层ID*/char* cfile_name; /*mute创建的文件*/ulint cline; /*mutex创建的文件行数*/ulint magic_n; /*魔法字*/
};
在自己定义mute_t的接口方法中,最核心的两个方法是:mutex_enter_func和mutex_exit方法
mutex_enter_func 获得mutex锁,假设mutex被其它线程占用。先会自旋SYNC_SPIN_ROUNDS,然后
mutex_exit 释放mutex锁。并向等待线程发送能够抢占mutex的信号量
3.1 mutex_enter_func流程图:
![](/assets/blank.gif)
3.2 mutex_exit流程图
![](/assets/blank.gif)
3.4 mutex_t的内存结构关系图
![](/assets/blank.gif)
![](/assets/blank.gif)
4 rw_lock的实现
1、同一时刻同意多个线程同一时候读取内存中的变量
2、同一时刻仅仅同意一个线程更改内存中的变量
3、同一时刻当有线程在读取变量时不同意不论什么线程写存在
4、同一时刻当有线程在更改变量时不同意不论什么线程读,也不同意出自己以外的线程写(线程内能够递归占有锁)。
5、当有rw_lock处于线程读模式下是有线程写等待,这时候假设再有其它线程读请求锁的时。这个读请求将处于等待前面写完毕。
S-latch | X-latch | |
S-latch | 兼容 | 不兼容 |
X-latch | 不兼容 | 不兼容 |
struct rw_lock_struct
{ulint reader_count; /*获得S-LATCH的读者个数,一旦不为0,表示是S-LATCH锁*/ulint writer; /*获得X-LATCH的状态。主要有RW_LOCK_EX、RW_LOCK_WAIT_EX、 RW_LOCK_NOT_LOCKED, 处于RW_LOCK_EX表示是一个x-latch锁,RW_LOCK_WAIT_EX的状态表示是一个S-LATCH锁*/ os_thread_id_t writer_thread; /*获得X-LATCH的线程ID或者第一个等待成为x-latch的线程ID*/ulint writer_count; /*同一线程中X-latch lock次数*/mutex_t mutex; /*保护rw_lock结构中数据的相互排斥量*/ulint pass; /*默觉得0,假设是非0,表示线程能够将latch控制权转移给其它线程,在insert buffer有相关的调用*/ ulint waiters; /*有读或者写在等待获得latch*/ibool writer_is_wait_ex;UT_LIST_NODE_T(rw_lock_t) list;UT_LIST_BASE_NODE_T(rw_lock_debug_t) debug_list;ulint level; /*level标示。用于检測死锁*//*用于调试的信息*/char* cfile_name; /*rw_lock创建时的文件*/ulint cline; /*rw_lock创建是的文件行位置*/char* last_s_file_name; /*最后获得S-latch时的文件*/char* last_x_file_name; /*最后获得X-latch时的文件*/ulint last_s_line; /*最后获得S-latch时的文件行位置*/ulint last_x_line; /*最后获得X-latch时的文件行位置*/ulint magic_n; /*魔法字*/
};
在rw_lock_t获得锁和释放锁的主要接口是:rw_lock_s_lock_func、rw_lock_x_lock_func、rw_lock_s_unlock_func、rw_lock_x_unlock_func四个关键函数。 当中rw_lock_s_lock_func和rw_lock_x_lock_func中定义了自旋函数,这两个自旋函数的流程和mutex_t中的自旋函数实现流程是相似的。其目的是要在自旋期间就完毕锁的获得。详细细节能够查看sync0rw.c中的rw_lock_s_lock_spin/rw_lock_x_lock_func的代码实现。从上面结构的定义和函数的实现能够知道rw_lock有四种状态:
RW_LOCK_NOT_LOCKED 空暇状态
RW_LOCK_SHARED 处于多线程并发都状态
RW_LOCK_WAIT_EX 等待从S-latch成为X-latch状态
RW_LOCK_EX 处于单线程写状态
![](/assets/blank.gif)
![](/assets/blank.gif)
5 死锁检測与调试
与死锁检測相关的模块主要是mutex level、rw_lock level和sync_cell。latch level相关的定义:
/*sync_thread_t*/struct sync_thread_struct{os_thread_id_t id; /*占用latch的thread的id*/sync_level_t* levels; /*latch的信息,sync_level_t结构内容*/};/*sync_level_t*/struct sync_level_struct{void* latch; /*latch句柄,是mute_t或者rw_lock_t的结构指针*/ulint level; /*latch的level标识ID*/};
在latch获得的时候,innodb会调用mutex_set_debug_info函数向sync_thread_t中增加一个latch被获得的状态信息。事实上就是包含获得latch的线程id、获得latch的文件位置和latch的层标识(详细的细节能够查看mutex_enter_func和mutex_spin_wait)。仅仅有占用了latch才会体如今sync_thread_t中,假设仅仅是在等待获得latch是不会增加到sync_thread_t其中的。innodb能够通过sync_thread_levels_empty_gen函数来输出全部latch等待依赖的cell_t序列。追踪线程等待的位置。
5.1sync_thread_t与sync_level_t的内存结构关系:
![](/assets/blank.gif)
levels的长度是SYNC_THREAD_N_LEVELS(10000)。
5.2死锁与死锁检測
线程A 线程B
mutex1 enter mutex2 enter
mutex2 enter mutex1 enter
运行任务 运行任务
mutex2 release mutex1 release
mutex1 release mutex2 release
上面两个线程同一时候执行的时候。可能产生死锁的情况。就是A线程获得了mutex1正在等待mutex2的锁。同一时候线程2获得了mutex2正在等待mutex1的锁。在这样的情况下,线程1在等线程2,线程2在等线程就造成了死锁。
1、将进入等待的latch相应的cell作为參数传入到sync_array_detect_deadlock其中,其中start的參数和依赖的cell參
2、进入sync_array_detect_deadlock先推断依赖的cell是否正在等待latch,假设没有,表示没有死锁。直接返回.
假设没有。继续将查询到的cell作为參数递归调用
这是个两函数交叉递归推断的过程。
由于关系数据库的latch使用很频繁和复杂。检查死锁对于锁的调试是很有效的,尤其是配合thread_levels状态信息输出来做调试,对死锁排查是很有意义的。
![](/assets/blank.gif)
6.总结
我个人理解主要是降低操作系统上下文的切换,提高并发的效率。innodb中实现的自己定义latch仅仅适合短时间的锁等待(最好不超过50us),假设是长时间锁等待,不妨使用操作系统提供的。尽管自己定义锁在等待一个自旋周期会进入操作系统的event_wait,但这无疑比系统的mutex lock耗费的资源多。最后我们还是看作者在代码中的总结:
MySQL系列:innodb源代码分析之线程并发同步机制相关推荐
- MySQL系列-高级-性能分析工具-EXPLAIN
MySQL系列-高级-性能分析工具-EXPLAIN 1. EXPLAIN概述 1.1 官网介绍 1.2 EXPLAIN 基本语法 2. 基于函数和存储过程插入数据 2.1 创建表 2.2 创建函数和过 ...
- MySQL系列-innodb doublewrite
DBW(double write) double wirte是innodb存储引擎为了保证数据页的安全性而引入的一项技术,那么数据库安全性从何说起呢? 就从数据磁盘的一次io是4KB说起,一项简单的命 ...
- Android系统进程间通信(IPC)机制Binder中的Server启动过程源代码分析
原文地址: http://blog.csdn.net/luoshengyang/article/details/6629298 在前面一篇文章浅谈Android系统进程间通信(IPC)机制Binder ...
- (三)线程同步工具集_1---控制线程并发访问一个资源
2019独角兽企业重金招聘Python工程师标准>>> 线程同步工具集 在前面了解了线程的同步机制,临界区等,了解了线程的两种基本的同步机制: synchronized关键字: Lo ...
- linux 线程管理、同步机制等
线程 学了那么多有关进程的东西,一个作业从一个进程开始,如果你需要执行其他的东西你可以添加一些进程,进程之间可以通信.同步.异步.似乎所有的事情都可以做了. 对的,进程是当初面向执行任务而开发出来的, ...
- 线程同步机制synchronized中锁的判断以及锁的作用范围
当我们使用多个线程访问同一资源(可以是同一个变量.同一个文件.同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题,但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题. 要 ...
- java多线程问题_【java 多线程】多线程并发同步问题及解决方法
一.线程并发同步概念 线程同步其核心就在于一个"同".所谓"同"就是协同.协助.配合,"同步"就是协同步调昨,也就是按照预定的先后顺序进行运 ...
- Java线程的同步和异步的区别
1. 同步与异步 多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线 程的处理的数据,而B线程又修改了A线程处理的数理.显然这是由于全局资源造成的,有时为了解 决此 ...
- 深入理解JVM之代码执行机制与线程资源同步及交互机制
Java规范定义标准结构如图3.1 Java代码的执行机制 Java源码编译机制 javac将Java源码编译为class文件的步骤如图3.2 1.分析和输入到符号表(Parse and Enter) ...
最新文章
- 南方科技大学宋毅课题组招聘启事
- R-C3D 视频活动检测的经典算法
- android的33种常用组件1
- 字体渲染 mac linux,Mac下通过命令来渲染字体
- vue工程本地代码请求http发生跨域提示错误解决方法
- python装饰器_python装饰器完全指南之一
- oracle可以使用提交完成的事务,【体系结构】Oracle数据提交与事务隔离实验 oradebug挂起lgwr进程...
- mysql数据库建新分区_mysql数据库分区
- 【Linux】一步一步学Linux——killall命令(125)
- 进程的并发与并行,三种状态
- C语言重新定位文件,C语言代码重定位 (原创)
- Android—开发过程中的相关注意事项
- AspNetCore微服务下的网关-Kong(一)
- P53:进化了8亿年的抑癌基因
- 记一次MBR锁机病毒分析
- Linux防火墙之介绍
- python逻辑判断_return逻辑判断表达式
- 《Java 开发从入门到精通》—— 2.4 Java的运行机制
- python 携程订单接口_携程api开发
- 电脑上怎么设置时间提醒?有哪些电脑桌面时钟提醒便签?
热门文章
- MaterialDesign动画
- 数据库常用操作(未完待续)
- unittest框架(三)unittest+yaml数据驱动
- if the parser found inconsistent certificates on the files in the .apk.104
- (学)DEV在设计界面部分组件显示红叉并报错的问题
- AT COMMAND的命令集
- linux多节点部署,Linux下docker部署+面板portainer管理多节点docker
- python实例变量初始化_Python – 应该在__init__中初始化所有成员变量
- npm run dev 后dist 被情况_npmamp;npxamp;nvmamp;yarn 介绍
- oracle 创建临时表报权限不足,ORACLE 临时表空间满了的原因解决方案