10月忙得像shi一样,不管怎样,忙里偷闲在这个月结束之前写上一篇,介绍InnoDB读写锁的实现
InnoDB读写锁特点
1. 读与读之间不冲突,读与写,写与写之间冲突,这个是读写锁最基本的特征
2. 申请写锁时,如果锁被读持有,将发生写锁等待,该写锁之后的所有读锁会等待该写锁成功获取后才能申请成功,这保证了公平:写锁请求不会被饿死
基本数据结构
sync_array_struct: 保存用于线程同步结构的数组,数组成员类型为sync_cell_struct
sync_cell_struct: 一个线程同步结构体,部分代码:
struct sync_cell_struct { void* wait_object; /*!< pointer to the object the thread is waiting for; if NULL the cell is free for use */ ulint request_type; /*!< lock type requested on the object */ os_thread_id_t thread; /*!< thread id of this waiting thread */ ibool waiting; /*!< TRUE if the thread has already called sync_array_event_wait on this cell */ ib_int64_t signal_count; /*!< We capture the signal_count of the wait_object when we reset the event. This value is then passed on to os_event_wait and we wait only if the event has not been signalled in the period between the reset and wait call. */ time_t reservation_time;/*!< time when the thread reserved the wait cell */ }; |
以上是部分成员,这个结构体与srv_slot_struct,srv_conc_slot_struct作用基本相同,其中包含一个同步事件:wati_object,其类型可以是:rw_lock_t和mutext_t,其内部都有一个os_event_t类型的成员event,可以对其执行set/reset/wait,想了解其具体实现,请参考代码
rw_lock_struct:读写锁结构体,部分成员:
typedef struct rw_lock_struct rw_lock_t; struct rw_lock_struct { volatile lint lock_word; /*!< Holds the state of the lock. */ volatile ulint waiters;/*!< 1: there are waiters */ volatile os_thread_id_t writer_thread; /*!< Thread id of writer thread. Is only guaranteed to have sane and non-stale value iff recursive flag is set. */ os_event_t event; /*!< Used by sync0arr.c for thread queueing */ os_event_t wait_ex_event; /*!< Event for next-writer to wait on. A thread must decrement lock_word before waiting. */ |
有两个os_event_t类型的成员:event和wait_ex_eveint,其中wait_ex_event的作用是:如果当前锁只被读者持有,之后第一个写锁请求将在这个事件上等待,当这些读者中最后一个unlock时,会唤醒等待这个事件上的写等待,其它申请锁时冲突的情况将在event上发生所等待,这个事件在写者unlock时去唤醒
神奇的lock_word
InnoDB实现公平读写锁是靠这个成员来保证的,它被初始化为:X_LOCK_DECR
/* We decrement lock_word by this amount for each x_lock. It is also the start value for the lock_word, meaning that it limits the maximum number of concurrent read locks before the rw_lock breaks. The current value of 0x00100000 allows 1,048,575 concurrent readers and 2047 recursive writers.*/ #define X_LOCK_DECR 0x00100000 |
rw_lock_struct支持recursive模式,本文不打算介绍这个,从注释中可以看出X_LOCK_DECR被初始为一个较大的值,那么它代表什么意思呢?
1. 成功申请一个读锁,lock_word减1,释放时加1
2. 成功申请一个写锁,lock_word减X_LOCK_DECR,释放时加X_LOCK_DECR
根据以上行为,lock_word的取值区间有:(recursive writers模式不包含在内)
1. lock_word = X_LOCK_DECR: 锁没有被持有
2. 0 < lock_word < X_LOCK_DECR: 锁上有X_LOCK_DECR-lock_word个读者
3. lock_word = 0: 锁上有一个写者
4. -X_LOCK_DECR < lock_word < 0: 锁被读者持有,但是上面存在一个写申请
原理: 对lock_word的操作被保证是原子操作,且只有当lock > 0 时才允许加读锁或者写锁,当锁上有X_LOCK_DECR-lock_word个读者时(情况2),之后第一个写请求会原子地将lock_word减去X_LOCK_DECR(情况3),这保证之后来到的读请求不会在该写锁被获取之前进入
LOCK/UNLOCK操作
rw_lock_x_lock_func
1. 原子操作(如果lock_word > 0, 将其减去X_LOCK_DECR),之后spin,等待lock_word=0,spin次数超过一定次数后,从sync_primary_wait_array中找到一个sync_cell,将其event指向rw_lock_struct中的wait_ex_event,等待被唤醒,满足lock_word=0时退出
2. lock_word <= 0(已经存在其他写锁请求),进入spin,等待lock_word >0, 之后从sync_primary_wait_array中找到一个sync_cell,将其event指向rw_lock_struct中的event,等待被唤醒,唤醒后,进入1中逻辑执行
rw_lock_x_unlock_func
1. 原子操作(将lock_word增加X_LOCK_DECR),唤醒event上等待的事件
rw_lock_s_lock_func
1. 原子操作(如果lock_word > 0, 将其减去1后返回),如果lock_word > 0,加锁成功,返回
2. lock_word <= 0,等待lock_word > 0,spin一定次数后,从sync_primary_wait_array中找到一个sync_cell,将其event指向rw_lock_struct中的event,等待被唤醒
rw_lock_s_unlock_func
原子操作(将lock_word增加1),如果lock_word为0,说明存在写锁等待,唤醒wait_ex_event
总结
1. 写锁申请由于当时有读者(lock_word > 0)无法申请成功时,将lock_word减成负数,在wait_ex_event上等待被唤醒
2. 写锁申请由于当时有写锁/写等待(lock_word <= 0)无法申请成功时,将在event上等待被唤醒,被唤醒后将最终进入1中逻辑
3. 最后一个读锁释放时,会唤醒wait_ex_event
4. 写锁释放时,会唤醒event