阅读 39

未归类系列(小记)

同步的底层原理

软件同步

所谓的锁,可以理解为内存中的一个整型数。它拥有两种状态:空闲状态和上锁状态。加锁时,判断锁是否空闲,如果空闲,修改为上锁状态,返回成功;如果已经上锁,则返回失败。解锁时,则把锁状态修改为空闲状态。

硬件同步

在硬件方面,CPU提供了原子操作、关闭中断(单核且非抢占内核)、锁内存总线。
CPU会通过对内存总线加锁的手段来解决多核同时获取锁的情况,它是怎么实现的呢? 在CPU芯片上有一个HLOCK Pin,可以通过发送指令来操作,将#HLOCK Pin电位拉低,并持续到这条指令执行完毕,从而将内存总线锁住,这样同一总线上的其他CPU就不能通过总线来访问内存了。 最开始这些功能是用来测试CPU的,后来被操作系统实现而封装成各种功能:同步代码块,信号量等。
简单的关闭中断在多核CPU环境下是行不通的。需硬件提供支持,硬件提供的原语操作:

test and set --原子操作

返回ptr指向的旧值,同时ptr的值更新为new。

// 伪代码
int TestAndSet(int *ptr, int new) {
	int old = *ptr;
	*ptr = new;
	return old;
}

// 自旋锁实现伪代码
typedef struct __lock_t {
	int flag;
} lock_t;
 
void init(lock_t *lock) {
	lock->flag = 0;
}
 
void lock(lock_t *lock) {
	while (TestAndSet(&lock->flag, 1) == 1) {
		; // spin-wait (do nothing)
	}		
}
 
void unlock(lock_t *lock) {
	lock->flag = 0;
}
复制代码

compare-and-swap --原子操作

检测ptr指定地址的值是否与expected相等;如果相等,就将ptr指向的内存地址更新为new值。如果不等的话,什么都不做。最后都返回actual值,从而可以让调用compare-and-swap指令的代码知道成功与否。

// 伪代码
int CompareAndSwap(int *ptr, int expected, int new) {
	int actual = *ptr;
	if (actual == expected) {
	    *ptr = new;
	}
	return actual;
}	

// 自旋锁实现伪代码
typedef struct __lock_t {
	int flag;
} lock_t;
 
void init(lock_t *lock) {
	// 0 indicates that lock is available, 1 that it is held
	lock->flag = 0;
}
 
void lock(lock_t *lock) {
	while (CompareAndSwap(&lock->flag, 0, 1) == 1) {
		; // spin-wait (do nothing)
	}
}

void unlock(lock_t *lock) {
	lock->flag = 0;
}
复制代码

fetch-and-add --原子操作

原子地将某一地址的值加1。它保证了所有线程的执行,一旦某个线程得到了他自己的ticket值,在将来的某一时刻肯定会被调度执行(一旦前面的那些线程执行完临界区并释放锁)。

// 伪代码
int FetchAndAdd(int *ptr) {
	int old = *ptr;
	*ptr = old + 1;
	return old;
}


// 排队锁实现伪代码
// 全局共享变量lock->turn用来决定轮到了哪个线程;
// 当对于某个线程myturn等于turn时,那就轮到了这个线程进入临界区。
// unlock简单地将turn值加1,由此下一个等待线程(如果存在的话)就可以进入临界区了。
typedef struct __lock_t {
	int ticket;
	int turn;
} lock_t;
 
void lock_init(lock_t *lock) {
	lock->ticket = 0;
	lock->turn = 0;
}
 
void lock(lock_t *lock) {
	int myturn = FetchAndAdd(&lock->ticket);
	while (lock->turn != myturn) {
		; // spin
	}		
}
 
void unlock(lock_t *lock) {
	FetchAndAdd(&lock->turn);
}
复制代码

OS的上层的同步手段都基于此机制