概述
最近对innodb内核相关内容比较感兴趣,后续会陆续更新一些内核相关的文章,本文主要简述内核的同步机制怎么做。实现和jdk类似。通过自旋+入队列阻塞+信号唤醒实现。基于mysql-5.0.15版本学习。
具体细节
mutex和rw_lock
innodb实现了mutex和rw_lock两种锁。rw_lock基于mutex实现(读写锁),因此两者底层原理几乎相同,只不过rw_lock在mutex的基础上做了一些逻辑。
rw_lock主要是为了进一步提高并发性能,同时避免竞态条件而设计的。
rw_lock分为两种:x-latch和s-latch
S-latch | X-latch | |
---|---|---|
S-latch | 兼容 | 不兼容 |
X-latch | 不兼容 | 不兼容 |
x-latch可重入。s-latch不可重入,因为s-latch重入的时候会与另外一个申请此资源x-latch锁的线程冲突,陷入死锁。
获取mutex锁逻辑
通过mutex_enter_func方法实现。先TAS,后自旋尝试,如果失败后加入到wait array中并阻塞线程。
UNIV_INLINE
void mutex_enter_func(
mutex_t *mutex, /* in: pointer to mutex */
const char *file_name, /* in: file name where locked */
ulint line) /* in: line where locked */
{
ut_ad(mutex_validate(mutex));
/* Note that we do not peek at the value of lock_word before trying
the atomic test_and_set; we could peek, and possibly save time. */
#ifndef UNIV_HOTBACKUP
mutex->count_using++;
#endif /* UNIV_HOTBACKUP */
if (!mutex_test_and_set(mutex))
{
return; /* Succeeded! */
}
mutex_spin_wait(mutex, file_name, line);
}
复制代码
通过mutex_test_and_set先尝试,失败后调用mutex_spin_wait。具体细节就是自旋若干次(20),失败后加入wait array。
Wait array
innodb有一个全局的wait array,初始化分配1000内存。每次自旋失败后会加入到这个队列。每当持有对应latch的线程释放后会调用sync_array_singnal_object方法唤醒等待线程。
void sync_array_signal_object(
sync_array_t *arr, /* in: wait array */
void *object) /* in: wait object */
{
sync_cell_t *cell;
ulint count;
ulint i;
sync_array_enter(arr);
arr->sg_count++;
i = 0;
count = 0;
while (count < arr->n_reserved)
{
cell = sync_array_get_nth_cell(arr, i);
if (cell->wait_object != NULL)
{
count++;
if (cell->wait_object == object)
{
sync_cell_event_set(cell);
}
}
i++;
}
sync_array_exit(arr);
}
复制代码
这个方法就是遍历全局的数组,如果有线程正在等待获取这个对象的锁,则设置对应cell的信号和event事件去释放锁。此时阻塞的多线程会被唤醒然后重新尝试去获取锁。
获取s-latch逻辑
先通过mutex加锁,然后执行rw_lock_s_lock_low,该方法主要是判断rw_lock的writer字段,如果是RW_LOCK_NOT_LOCKED则直接成功。否则调用rw_lock_s_lock_spin去获取。
UNIV_INLINE
void
rw_lock_s_lock_func(
rw_lock_t* lock, /* in: pointer to rw-lock */
ulint pass, /* in: pass value; != 0, if the lock will
be passed to another thread to unlock */
const char* file_name,/* in: file name where lock requested */
ulint line) /* in: line where requested */
{
mutex_enter(rw_lock_get_mutex(lock));
if (UNIV_LIKELY(rw_lock_s_lock_low(lock, pass, file_name, line))) {
mutex_exit(rw_lock_get_mutex(lock));
return; /* Success */
} else {
/* Did not succeed, try spin wait */
mutex_exit(rw_lock_get_mutex(lock));
rw_lock_s_lock_spin(lock, pass, file_name, line);
return;
}
}
复制代码
rw_lock_s_lock_spin主要就是自旋尝试后丢队列。
唤醒操作类似不再赘述。
死锁问题
通过latch-level避免了死锁的产生。
类似上图描述,只要按照顺序去加锁,就不会出现死锁的现象。
在debug模式下,可进行死锁的检测。通过深度优先算法检测是否有环即可。
总结
innodb的同步机制比较传统,通过TAS➕自旋➕wait array实现。后续会继续更新innodb相关内容。敬请关注