对顺序锁的思考

今天读到顺序锁的概念,似乎对之前被问到的锁优化问题,有了一些新的理解,可以用这个来解决。其主要使用的套路是一个版本号和内存屏障。版本号用来保证写的可见性,而内存屏障在一定程度上保证顺序性。
先来说说他为什么满足线程安全吧。
首先原子性,顺序锁同一时间,只有一个写在临界区操作,所以满足原子性的要求
其次是顺序性,读操作在执行完之后会,重新读取版本,判断执行的过程中是否有写操作,从而保证顺序性。
最后是可见性,由临界区的内存在保证
先定义一下顺序锁的数据结构吧.

typedef struct {
	spinlock_t lock; // 用来锁住写,临界区只有一个写
	int seq; //版本号控制,后续详解
}seq_lock_t;

说要顺序锁,可以分为读写两方。那么先来看写的一方是怎么做的?

//锁操作如下
void pthread_seq_lock(seq_lock_t* seq_lock) {
	pthread_spin_lock(seq_lock->lock ); // 只有一个写可以进入临界区
	++seq_lock->seq;//加一个版本号,如果版本号为奇数,表示有写操作在临界区
	smp_wb(); //内存屏障,对这块内存保持串行。
}

void pthread_seq_unlock(seq_lock_t* seq_lock) {
	smp_wb(); //表示在这块临界区之前,对内存操作是串行的。
	++seq_lock->seq;  /加一个版本号,如果版本号为奇数,表示有写操作在临界区
	pthread_spin_unlock(seq_lock->lock);
}

那么读的时候是不会加锁的如下:

unsigned read_seqbegin(seq_lock_t* seq_lock)
{
	unsigned ret;
	while (true) {
		ret = seq_lock->seq;  // 获取当前的序列号
		if (unlikely(ret & 1)) {  // 判断当前序列号为奇数,则目前正在写操作
			cpu_relax(); // cpu pause 一下
			conitnue; 
		}else{
			break;
		}
	}
	smp_rmb(); // 内存屏障
	return ret;
}

int read_seqretry(seq_lock_t* seq_lock, unsigned start)
{
	smp_rmb();// 内存屏障
	return unlikely(seq_lock->seq != start); //判断在读的过程中是否有写操作
}

那么在使用的时候写和读分别如下

pthread_seq_lock(&seqlock);
write_something();    // 写操作代码块
pthread_seq_unlock(&seqlock);


do{
    seqnum = read_seqbegin(&seqlock);  // 读执行单元在访问共享资源时要调用该函数,返回锁seqlock的顺序号 
    read_something(); // 读操作代码段 
} while( read_seqretry(&seqlock, seqnum)); // 在读结束后调用此函数来检查,是否有写执行单元对资源进行操作,若有则重新读。

这种顺序锁适合,\color{red}{写少读多的情景},读的场景下,不需要每次都锁,只需要判断一下版本号,如果版本号为偶数,便能直接访问内存,然后在结尾的时候通过版本号判断下,在读的过程中是否有写的过程。如果有写的过程则可以重新执行一遍读,缺点就是这里,读操作最好幂等。

但是我还存在一个问题在于,在顺序锁的机制下,读写不是都能访问同一片内存临界区么,会不会有core dump?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章