MySQL · 源码阅读 · MySQL8.0 innodb锁相关

背景

innodb里面的mutex常见的实现是PolicyMutex<TTASEventMutex,信号量底层使用是os_event_t

代码分析

os_event_t

  1. struct os_event {
  2. void set();
  3. int64_t reset();
  4. void wait_low();
  5. void broadcast();
  6. private:
  7. bool m_set;
  8. int64_t signal_count;
  9. os_cond_t cond_var;
  10. EventMutex mutex;
  11. os_cond_t cond_var;
  12. }
  1. set函数 如果m_set是false,调用broadcast函数
  2. reset函数 设置m_set = false, 返回signal_count
  3. wait_low函数 m_set == false 并且signal_count == reset_sig_count 才进入wait, 保证不会死锁
  4. broadcast函数 m_set设置true, signal_count计数器+1,唤醒其他等待者

wait操作: 先调用reset函数,然后用返回的reset_sig_count作为参数,调用wait_low函数
signal操作: 调用set函数

PolicyMutex

先看PolicyMutex的主要结构

  1. template <typename MutexImpl>
  2. struct PolicyMutex {
  3. private:
  4. MutexImpl m_impl;
  5. public:
  6. void enter();
  7. void exit();
  8. void init();
  9. }

init函数负责初始化,加锁是enter函数,解锁是exit函数,具体的实现都是通过m_impl来实现

TTASEventMutex

在看TTASEventMutex的主要结构

  1. struct TTASEventMutex {
  2. public:
  3. void init();
  4. void exit();
  5. void enter();
  6. bool try_lock();
  7. private:
  8. std::atomic_bool m_lock_word;
  9. std::atomic_bool m_waiters;
  10. os_event_t m_event;
  11. MutexPolicy m_policy;
  12. }
  1. m_lock_word 判断是否加锁成功
  2. m_waiters 当前锁有多少个等待者
  3. m_event 线程等待内部实现使用
  4. m_policy 统计使用

exit函数

  1. m_lock_word设置false
  2. 如果有waiter就调用signal函数唤醒其他等待者

enter函数功能就是加锁,成功返回,否则就一直等待,具体内部实现:

  1. 第一步会优先判断m_lock_word原子变量cas操作能否成功,成功就说明加锁成功了,否则说明有其他人持有锁
  2. 调用spin_and_try_lock函数,内部实现死循环执行下面步骤:

    2.1. 先尝试max_spins次对m_lock_word变量执行cas操作,如果成功就返回
    2.2. 没有成功就先尝试执行yield函数,放弃cpu占用
    2.3. 调用wait函数,内部实现:

    1. 2.3.1. 先调用sync_array_get_and_reserve_cellwait_array获取一个cellm_waiters设置为true
    2. 2.3.2. 尝试4m_lock_word原子变量cas操作,如果成功就返回
    3. 2.3.3. 调用sync_array_wait_event等待信号量唤醒

sync_array_t

  1. struct sync_array_t {
  2. ulint n_reserved; //正在使用的cell个数
  3. ulint n_cells; //数组分配大小
  4. sync_cell_t *cells; //数组
  5. ulint next_free_slot; //除了free list以外,下一个可以用的cell
  6. ulint first_free_slot; //free list链表头, 复用cell里面的line字段作为next指针
  7. }

sync_array_init 初始化sync_wait_array 二维数组,第一维大小1,第二维大小100k。
sync_array_reserve_cell 从sync_wait_array 里面获取一个free cell,极限情况全部cell被占用就返回nullptr
sync_array_free_cell 放回cell到free list
sync_array_wait_event 等待信号量唤醒

GenericPolicy

  1. struct GenericPolicy {
  2. latch_id_t m_id;
  3. /** Number of spins trying to acquire the latch. */
  4. uint32_t m_spins;
  5. /** Number of waits trying to acquire the latch */
  6. uint32_t m_waits;
  7. /** Number of times it was called */
  8. uint32_t m_calls;
  9. }

每次加锁都会更新里面相关字段,因此通过GenericPolicy可以看到锁竞争激烈程度