MySQL源代码:如何对读写锁进行处理(一)

2014-11-24 11:03:24 · 作者: · 浏览: 0
转载请署名:印风
-----------------------------------------------------------
最近碰到一个问题,线上一台机器在等待信号量时间过长, mysql的监控线程认为此时mysqld已经hang住了,于是自杀重启。这里涉及到一个有趣的问题,也就是mysql如何对读写锁进行处理。
主要包括三个部分:
1. 建锁
2. 加锁
3. 解锁
4. 监控锁
以下内容基于Percona5.5.18进行分析

1.创建锁
锁的创建实际上就是初始化一个RW结构体(rw_lock_t),实际调用函数如下:

# define rw_lock_create(K, L, level) \
rw_lock_create_func((L),#L)

在rw_lock_create上有三个参数,在实际场景锁时只用到第2个参数
其中K表示mysql_pfs_key_t,level显示当前的操作类型(起码看起来是的,在文件sync0sync.h中定义),看起来k是为performance schema准备的,而k代表了当前操作所在的层次。
例如:purge线程的读写锁创建:

rw_lock_create(trx_purge_latch_key,
&purge_sys->latch,SYNC_PURGE_LATCH);

我们进去rw_lock_create_func看看到底是怎么创建的。
可以看到这个函数的逻辑其实很简单:
lock->lock_word =X_LOCK_DECR; //关键字段
用于限制读写锁的最大并发数,代码里的注释如下:

/* We decrement lock_word by this amountfor each x_lock. It is also the
start value for the lock_word, meaning thatit limits the maximum number
of concurrent read locks before the rw_lockbreaks. The current value of
0x00100000 allows 1,048,575 concurrentreaders and 2047 recursive writers.*/

在尝试加锁时会调用rw_lock_lock_word_decr减少lock_word
在初始化一系列变量后,执行:

lock->event = os_event_create(NULL);
lock->wait_ex_event = os_event_create(NULL);
os_event_create用于创建一个 系统信号,实际上最终创建的还是互斥量(os_fast_mutex_init(&(event->os_mutex));以及条件变量(os_cond_init(&(event->cond_var));)
最后将lock加入到全局链表rw_lock_list中

2.加锁
加锁函数由宏定义,实际调用函数为:
1)写锁

# define rw_lock_x_lock(M) \
rw_lock_x_lock_func((M),0, __FILE__, __LINE__)

当申请写锁时,执行如下步骤:
(1).调用rw_lock_x_lock_low函数去获取锁,如果得到锁,则rw_x_spin_round_count += i后直接返回,如果得不到锁,继续执行
(2).loop过程中只执行一次rw_x_spin_wait_count++
(3).在毫秒级别的loop多次等待

while (i < SYNC_SPIN_ROUNDS
&& lock->lock_word <= 0) {
if(srv_spin_wait_delay) {
ut_delay(ut_rnd_interval(0,
srv_spin_wait_delay));
}
i++;
}

这里涉及到两个系统变量:
innodb_sync_spin_loops(SYNC_SPIN_ROUNDS)
innodb_spin_wait_delay(srv_spin_wait_delay)

在SYNC_SPIN_ROUNDS循环里调用函数ut_delay,这个函数很简单,就是做了delay*50次空循环

Ut_delay(uint delay):
for(i = 0; i < delay * 50; i++) {
j+= i;
UT_RELAX_CPU();
}
其中,UT_RELAX_CPU()会调用汇编指令来独占CPU,以防止线程切换
(4).如果loop的次数等于SYNC_SPIN_ROUNDS,调用os_thread_yield(实际调用pthread_yield,导致调用线程放弃CPU的占用)将线程挂起;否则挑到1继续loop
(5).在sync_primary_wait_array里获取一个cell(占个坑?)。调用sync_array_reserve_cell,看起来有1000个坑位(sync_primary_wait_array->n_cells)
(6).再次调用rw_lock_x_lock_low函数尝试获取锁,若成功获得,则返回
(7).调用sync_array_wait_event等待条件变量,然后返回1继续loop
具体的加锁函数(rw_lock_x_lock_low)稍后分析

2)读锁

# define rw_lock_s_lock(M) \
rw_lock_s_lock_func((M),0, __FILE__, __LINE__)

这个函数定义在sync0rw.ic里,函数也很简单,如下:

if (rw_lock_s_lock_low(lock, pass, file_name, line)) {
return; /* Success */
}else {
/* Did not succeed, try spin wait */
rw_lock_s_lock_spin(lock, pass, file_name, line);
return;
}

这里首先调用rw_lock_s_lock_low进行加锁,如果