?專欄內(nèi)容:
postgresql內(nèi)核源碼分析
手寫數(shù)據(jù)庫(kù)toadb
并發(fā)編程
個(gè)人主頁(yè):我的主頁(yè)
座右銘:天行健,君子以自強(qiáng)不息;地勢(shì)坤,君子以厚德載物.
========================================
概述
在postgresql 中,有大量的并發(fā)同步,所以避免不了使用很多保護(hù)鎖。
同時(shí)為了提升并發(fā)的性能,針對(duì)不同場(chǎng)景下的加鎖需求,設(shè)計(jì)了:
- spinlock 自旋鎖
- lightweight lock(LWLocks) 輕量級(jí)鎖
- regular lock(a/k/a heavyweight locks) 普通鎖
- SIReadLock predicate locks 謂詞鎖
本文主要針對(duì)這四種鎖進(jìn)行分享,起拋磚引玉的作用。
spinlock
是一種持有時(shí)間非常短的鎖。它是通過(guò)test and set 原子操作來(lái)實(shí)現(xiàn)。
通過(guò)一定時(shí)間內(nèi)的檢測(cè),如果沒(méi)有持有就獲得,這個(gè)時(shí)間大概是1min,超時(shí)就會(huì)導(dǎo)致ERR錯(cuò)誤。
所以此類鎖,都是一些狀態(tài)保護(hù),很快就釋放,中間沒(méi)有IO,大的內(nèi)存操作。它的實(shí)現(xiàn)依賴于操作系統(tǒng)的原子操作實(shí)現(xiàn),所以通過(guò)宏定義共公接口,底層根據(jù)不同操作系統(tǒng)實(shí)現(xiàn)不同。
也可以說(shuō)是一種無(wú)鎖化的實(shí)現(xiàn),需要原子操作TAS和內(nèi)存同步。
操作函數(shù)
#define SpinLockInit(lock) S_INIT_LOCK(lock)
#define SpinLockAcquire(lock) S_LOCK(lock)
#define SpinLockRelease(lock) S_UNLOCK(lock)
#define SpinLockFree(lock) S_LOCK_FREE(lock)
底層操作函數(shù)有這四個(gè),是通過(guò)宏定義給出,對(duì)于不同操作系統(tǒng)下,定義了具體的原子操作。
在支持TAS 原語(yǔ)的操作系統(tǒng)上,用TAS來(lái)實(shí)現(xiàn),如加鎖的函數(shù)如下
int s_lock(volatile slock_t *lock, const char *file, int line, const char *func)
{
SpinDelayStatus delayStatus;
init_spin_delay(&delayStatus, file, line, func);
while (TAS_SPIN(lock))
{
perform_spin_delay(&delayStatus);
}
finish_spin_delay(&delayStatus);
return delayStatus.delays;
}
#define TAS_SPIN(lock) (*(lock) ? 1 : TAS(lock))
static __inline__ int
tas(volatile slock_t *lock)
{
slock_t _res = 1;
__asm__ __volatile__(
" lock \n"
" xchgb %0,%1 \n"
: "+q"(_res), "+m"(*lock)
: /* no inputs */
: "memory", "cc");
return (int) _res;
}
可以看到核心代碼是通過(guò)匯編實(shí)現(xiàn)TAS操作,大致流程是這樣:
- 檢測(cè)lock是否為0 ,如果不為0,說(shuō)明還沒(méi)有解鎖,繼續(xù)等,直到超時(shí);
- 如果已經(jīng)解鎖,就走入?yún)R編代碼;鎖定總線,通過(guò)xchgb 原子交換lock和_res=1 兩個(gè)值,進(jìn)行內(nèi)存同步;加鎖成功;
- 此時(shí)TAS_PIN返回0,等待結(jié)束;
而slock_t 是什么類型呢?
如果在支持TAS指令的操作系統(tǒng)下是如下定義
typedef unsigned char slock_t;
是一個(gè)字節(jié),這樣可以很快的檢測(cè)和原子交換賦值
注意事項(xiàng)
通過(guò)上面的原理介紹,可以看到它等待的時(shí)間非常短,這就是說(shuō)在鎖持有時(shí),不能占用太久時(shí)間。
因此,在持有spinlock時(shí),只是一些狀態(tài)的獲取和賦值,就要立即釋放,否則就會(huì)有大量超時(shí)。
在鎖持有此間,避免磁盤,網(wǎng)絡(luò),函數(shù)調(diào)用等其它額外操作。
輕量級(jí)鎖 lightweight lock
介紹
輕量級(jí)鎖將加鎖過(guò)程分成了兩個(gè)階段,第一階段通過(guò)原子操作來(lái)檢測(cè),如果可以加鎖,就加鎖成功;如果不能加鎖,進(jìn)入第二階段,將自己加入等待隊(duì)列,并阻塞在信號(hào)量上;
主要用于共享內(nèi)存和數(shù)據(jù)塊的操作保護(hù)
它因?yàn)榉至藘蓚€(gè)階段,所以較一般的系統(tǒng)級(jí)鎖性能更高效一些。
它提供了如下特點(diǎn):
- 能夠快速檢測(cè)鎖狀態(tài),并且獲取到鎖;
- 每個(gè)后臺(tái)進(jìn)程只能有一個(gè)排隊(duì)中的輕量級(jí)鎖;
- 在持有鎖期間,信號(hào)會(huì)被阻塞
- 在錯(cuò)誤時(shí)會(huì)釋放鎖;
數(shù)據(jù)結(jié)構(gòu)
typedef struct LWLock
{
uint16 tranche; /* tranche ID */
pg_atomic_uint32 state; /* state of exclusive/nonexclusive lockers */
proclist_head waiters; /* list of waiting PGPROCs */
#ifdef LOCK_DEBUG
pg_atomic_uint32 nwaiters; /* number of waiters */
struct PGPROC *owner; /* last exclusive owner of the lock */
#endif
} LWLock;
extern bool LWLockAcquire(LWLock *lock, LWLockMode mode);
extern bool LWLockConditionalAcquire(LWLock *lock, LWLockMode mode);
extern bool LWLockAcquireOrWait(LWLock *lock, LWLockMode mode);
extern void LWLockRelease(LWLock *lock);
初始化
加鎖
- 判斷是否已經(jīng)持有鎖數(shù)量,超過(guò)上限;阻塞信號(hào)中斷;
- 第一階段 嘗試加鎖,加上時(shí)直接返回鎖;否則將自己放入等待隊(duì)列;再次嘗試加鎖;
- 第二階段 如果仍沒(méi)有獲取到鎖時(shí),在當(dāng)前backend對(duì)應(yīng)的 MyProc中的信號(hào)量上進(jìn)行等待;
直到被喚醒,如果proc->lwWaiting == LW_WS_NOT_WAITING時(shí),繼續(xù)等待;
- 當(dāng)獲取到鎖時(shí),將鎖加入自己持有鎖的數(shù)組中記錄;
解鎖
從本等數(shù)據(jù)中獲取當(dāng)前鎖的加鎖模式; 從鎖中解除;
如果有等待者,將它們從等待隊(duì)列中移除,然后喚醒它們;等待者們將再次競(jìng)爭(zhēng);
等待鎖釋放
bool
LWLockAcquireOrWait(LWLock *lock, LWLockMode mode);
- 介紹
這個(gè)接口有點(diǎn)意思,即可以獲取鎖,也用來(lái)等待別人釋放鎖;
當(dāng)前鎖如果沒(méi)有被占用,則占有鎖后函數(shù)返回;
如果當(dāng)前鎖被占用,則等待鎖,等別人釋放鎖后,就直接返回,而不持有鎖。
- 用途
這個(gè)函數(shù)主要用來(lái)在寫WAL時(shí),獲取鎖,因?yàn)橥瑫r(shí)只能有一個(gè)進(jìn)程寫WAL;
如果當(dāng)前沒(méi)有人寫WAL,則持有鎖后,執(zhí)行WAL寫入。
如果當(dāng)前已經(jīng)有人持有鎖,在寫WAL,那么自己的WAL也會(huì)被寫入,因?yàn)閃AL是順序?qū)懭?,后寫時(shí),需要把前面的內(nèi)容都要寫入。
條件變量
static bool LWLockConflictsWithVar(LWLock *lock,
uint64 *valptr, uint64 oldval, uint64 *newval,
bool *result)
bool LWLockWaitForVar(LWLock *lock, uint64 *valptr, uint64 oldval, uint64 *newval);
void LWLockUpdateVar(LWLock *lock, uint64 *valptr, uint64 val);
基于輕量級(jí)鎖,又實(shí)現(xiàn)了一組類似于條件變量的接口;
LWLockWaitForVar檢測(cè)變量是否變化,如果沒(méi)人持有鎖,那就直接返回;如果有鎖,則等待,直到鎖釋放后,返回新值;
LWLockUpdateVar是改變變量的值,并通知等待者,喚醒等待者;
鎖排隊(duì)
lightweiht lock可能會(huì)長(zhǎng)時(shí)間等待,因此每個(gè)backend只能有一個(gè)正在等待的輕量級(jí)鎖,所以每個(gè)backend都會(huì)有一個(gè)信號(hào)量;
struct PGPROC
{
// other members ...
PGSemaphore sem; /* ONE semaphore to sleep on */
// other members ...
};
信號(hào)量定義在PROC結(jié)構(gòu)上,當(dāng)進(jìn)入信號(hào)量等待時(shí),同時(shí)也會(huì)把自己的MyProc添加到 lock->waiters 列表成員中。
在鎖持有者釋放鎖時(shí),會(huì)刪除隊(duì)列中的所有成員,同時(shí)喚醒等待者的信號(hào)量;
在介紹了排隊(duì)和釋放后,就會(huì)發(fā)現(xiàn)它存在兩個(gè)問(wèn)題:
- 等鎖的餓死問(wèn)題
- 驚群?jiǎn)栴}
當(dāng)然lwlock 隊(duì)列的喚醒也是順序喚醒,同時(shí)加鎖分為兩階段,這就在一定程度上避免了上述問(wèn)題。
另外lwlock加鎖是非常頻,可能在很短時(shí)間有加鎖/釋放,所以需要更簡(jiǎn)潔直接的加鎖方式。
結(jié)尾
非常感謝大家的支持,在瀏覽的同時(shí)別忘了留下您寶貴的評(píng)論,如果覺(jué)得值得鼓勵(lì),請(qǐng)點(diǎn)贊,收藏,我會(huì)更加努力!
作者郵箱:study@senllang.onaliyun.com
如有錯(cuò)誤或者疏漏歡迎指出,互相學(xué)習(xí)。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-537244.html
注:未經(jīng)同意,不得轉(zhuǎn)載!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-537244.html
到了這里,關(guān)于postgresql內(nèi)核分析 spinlock與lwlock原理與實(shí)現(xiàn)機(jī)制的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!