今天讀到順序鎖的概念,似乎對之前被問到的鎖優化問題,有了一些新的理解,可以用這個來解決。其主要使用的套路是一個版本號和內存屏障。版本號用來保證寫的可見性,而內存屏障在一定程度上保證順序性。
先來說說他爲什麼滿足線程安全吧。
首先原子性,順序鎖同一時間,只有一個寫在臨界區操作,所以滿足原子性的要求
其次是順序性,讀操作在執行完之後會,重新讀取版本,判斷執行的過程中是否有寫操作,從而保證順序性。
最後是可見性,由臨界區的內存在保證
先定義一下順序鎖的數據結構吧.
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)); // 在讀結束後調用此函數來檢查,是否有寫執行單元對資源進行操作,若有則重新讀。
這種順序鎖適合,,讀的場景下,不需要每次都鎖,只需要判斷一下版本號,如果版本號爲偶數,便能直接訪問內存,然後在結尾的時候通過版本號判斷下,在讀的過程中是否有寫的過程。如果有寫的過程則可以重新執行一遍讀,缺點就是這裏,讀操作最好冪等。
但是我還存在一個問題在於,在順序鎖的機制下,讀寫不是都能訪問同一片內存臨界區麼,會不會有core dump?