Innodb同步机制(内核相关)

概述

最近对innodb内核相关内容比较感兴趣,后续会陆续更新一些内核相关的文章,本文主要简述内核的同步机制怎么做。实现和jdk类似。通过自旋+入队列阻塞+信号唤醒实现。基于mysql-5.0.15版本学习。

具体细节

mutex和rw_lock

innodb实现了mutexrw_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方法唤醒等待线程。

innodb-sync.png

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避免了死锁的产生。

2E98A89A-A694-4C1B-ACD1-9E4F0B83817A.png
类似上图描述,只要按照顺序去加锁,就不会出现死锁的现象。
在debug模式下,可进行死锁的检测。通过深度优先算法检测是否有环即可。

总结

innodb的同步机制比较传统,通过TAS➕自旋➕wait array实现。后续会继续更新innodb相关内容。敬请关注

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享