對順序鎖的思考

今天讀到順序鎖的概念,似乎對之前被問到的鎖優化問題,有了一些新的理解,可以用這個來解決。其主要使用的套路是一個版本號和內存屏障。版本號用來保證寫的可見性,而內存屏障在一定程度上保證順序性。
先來說說他爲什麼滿足線程安全吧。
首先原子性,順序鎖同一時間,只有一個寫在臨界區操作,所以滿足原子性的要求
其次是順序性,讀操作在執行完之後會,重新讀取版本,判斷執行的過程中是否有寫操作,從而保證順序性。
最後是可見性,由臨界區的內存在保證
先定義一下順序鎖的數據結構吧.

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?

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