MySQL 同步机制
InnoDB没有使用操作系统同步机制,而是自己封装,通过spin(自旋)和wait array(等待队列)的设计提高性能
- test-and-set(TAS)指令
- spin lock
- mutex和自旋
- rw-lock
- wait array
test-and-set(TAS)指令
目前的CPU都支持TAS指令。该指令通过读取一个字节或者一个word,然后和0比较,并且无条件的将其在内存中的值设为1,是原子操作。
用到swap-atomic操作,将内存中与寄存器中的值交换。
int test_and_set(volatile int* addr){
int old_value;
old_value = swap_atomic(addr,1); //取值 并 赋1
if(old_value == 0){
return 0;
}
return 1;
}
spin lock
在TAS的基础上,实现的 一种简单的使用广泛的一种互斥结构。适合于较短的代码(代码过长会导致CPU的等待时间过长,造成CPU的浪费)。
//利用spin lock上锁
void lock(volatile lock_t* lock_status){
while(test_and_set(lock_status) == 1);
}
若lock_status的值为0,TAS返回0,上锁成功。若lock_status=1,表示对象正在被使用,进入循环,直到对象释放锁。
unlock操作,只需将lock_status置为0即可
mutex和自旋
InnoDB的mutex对象采用test-and-set命令,和spin lock类似。不同之处在于,若TAS返回1,经行自旋操作,一段时间之后若还得不到mutex则进入wait array,等待唤醒。
自旋的目的:
- 减少对内存的访问
- 减少上下文的切换
进入/唤出 wait array 都会产生上下文切换(耗时较长)
部分代码:
void mutex_enter_func(mutex_t* mutex){
if(!mutex_test_and_set(mutex)){
get mutex;
return;
}
loop:
//自旋
while(mutex_get_lock_word(mutex) != 0 && i < SYNC_SPIN_ROUNDS){
spin;
i ++
}
if (mutex_test_and_set(mutex) == 0) {
get mutex;
return;
}
sync_array_reserve_cell(mutex); // reserve a cell from wait array
mutex_set_waiters(mutex, 1);
for (i = 0; i < 4; i++) {
if (mutex_test_and_set(mutex) == 0) {
get mutex;
sync_array_free_cell(wait_array,index); //free cell
return;
}
}
sync_array_wait_event(wait_array,index);
goto loop;
}
自旋后sync_array_reserve_cell(mutex); 从wait array中分配一个cell,并将mutex->waiters置为1。(waiters表示是否有线程正在等待)然后再循环4次进行TAS操作,检测是否能获得mutex。原因是:
结果没有线程执行,且waiters = 1 导致无限等待。
rw-lock
在线程共享资源时应设置读/写锁(rw-lock也称latch)
- s-latch:共享锁,允许共同访问资源。(读)
- x-latch:排它锁,只允许一个线程访问资源。(写)
\ | S | X |
---|---|---|
S | 兼容 | 兼容 |
X | 不兼容 | 不兼容 |
x-latch与mutex一样,在得不到latch的时候,先进行自旋,获取不到再进入wait array。
s-latch有所不同,s-latch不支持递归,即一个线程不能执行两个s-latch。自旋的时候要判断,最后加进wait array。
wait array
wait array 由多个cell组成。 每个cell 保存等待唤醒的线程。有一个全局的wait array 对象,sync_primary_wait_array用于对等待latch的线程经行唤醒操作。由sync_array_create初始化,默认建立一个由1000(OS_THREAD_MAX_n)个cell的数组。
由sync_array_reserve_cell();为wait array分配cell,每次分配时扫描整个数组。由sync_array_sigal_object检查队列中是否有线程正在等待,由sync_array_free_cell(); 唤醒线程。
[技术支持]:《MySQL内核InnoDB储存引擎(卷一)》