设为首页 加入收藏

TOP

MySQL系列:innodb引擎分析之线程并发同步机制(一)
2014-11-23 19:49:18 来源: 作者: 【 】 浏览:88
Tags: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

在innodb引擎当中,封装了操作系统提供的基本mutex(互斥量)和event(信号量),在WINDOWS下的实现暂时不做记录,主要还是对支持POSIX系统来做介绍。在POSIX系统的实现是os_fast_mutex_t和os_event_t。os_fast_mutex_t相对简单,其实就是pthread_mutex。定义如下:
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的两线程信号控制的例子流程:
\
对于系统的封装,最主要的就是os_event_t接口的封装,而在os_event_t的封装中,os_event_set、os_event_reset、os_event_wait这三 个方法是最关键的。

2 CPU原子操作

在innodb的mutex(互斥量)的实现中,除了引用系统的os_mutex_t以外,还使用了原子操作来进行封装一个高效的mutex实现。在 系统支持原子操作的情况下,会采用自己封装的mutex来做互斥,如果不支持,就使用os_mutex_t。在gcc 4.1.2之前,编译器是 不提供原子操作的API的,所以在MySQL-.3.23的innodb中自己实现了一个类似__sync_lock_test_and_set的实现,代码是采用 了汇编实现:
  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的实现

在innodb中,带有原子操作的mutex自定义互斥量是基础的并发和同步的机制,目的是为了减少CPU的上下文切换和提供高效率,一般mutex等待的时间不超过100微秒的条件下,这种mutex效率是非常高的。如果等待的时间长,建议选择os_mutex方式。虽然自定义mutex在自旋时间超过自旋阈值会进入信号等待状态,但是整个过程相对os_mutex来说,效率太低,这不是自定义mutex的目的。自定义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流程图:


\

以上流程主要是在mutex_spin_wait这个函数中实现的,从其代码中可以看出,这个函数是尽力让线程在自旋周期内获得锁,因为一旦进入cell_wait状态,至少的耗费1 ~ 2次系统调用,在cell_add的时候有可能触发os_mutex_t的锁等待和一定会event_wait等待。这比系统os_mutex效率会低
首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇VirtualBox实现宿主机和虚拟机之.. 下一篇C语言访问MySQL数据库的方法

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: