目的
在源碼分析mysql多線程操作時,mysql除了使用通常意義上的rwlock,來進行讀寫控制,還使用了一種讀優先的rwlock對元數據鎖(MDL,Meta Data Lock)進行讀寫控制。以下內容中,對mysql的讀寫鎖進行深入的分析,更深刻的了解mysql的讀寫鎖。
數據結構
mysql在多線程處理中,mysql讀寫鎖數據結構mysql_rwlock_t的定義在mysql_thread.h和my_pthread.h頭文件中,具體定義如下所示:
typedef struct st_mysql_rwlock mysql_rwlock_t;
struct st_mysql_rwlock
{
/** The real rwlock */
rw_lock_t m_rwlock;
/** The instrumentation hook.*/
struct PSI_rwlock *m_psi;
};
#define rw_lock_t my_rw_lock_t
/*
On systems which don't support native read/write locks we have
to use own implementation.
*/
typedefstructst_my_rw_lock_t {
pthread_mutex_t lock; /* lock for structure*/
pthread_cond_t readers; /* waiting readers*/
pthread_cond_t writers; /* waiting writers*/
intstate; /* -1:writer,0:free,>0:readers*/
intwaiters; /* number of waiting writers*/
#ifdefSAFE_MUTEX
pthread_twrite_thread;
#endif
} my_rw_lock_t;
通過以上定義可知,真正的rwlock根據不同的環境,使用不同的處理函數,但是處理的邏輯和思想是是一致的。以上是mysql自身實現的rwlock結構體my_rw_lock_t的定義。從定義可知,該結構定義了兩個條件變量readers、writers,分別用於多線程處理中,讀者和寫者獲取鎖的條件判斷。並且,定義state變量用於標示當前狀態,定義waiters變量用於記錄當前等待的寫者數量。
除此之外,mysql還定義了讀優先的rwlock,定義為pr_lock,用於控制MDL的多線程讀寫。具體定義如下所示:
typedef struct st_mysql_prlock mysql_prlock_t;
struct st_mysql_prlock
{
/** The real prlock */
rw_pr_lock_t m_prlock;
/** The instrumentation hook. */
struct PSI_rwlock *m_psi;
};
typedef struct st_rw_pr_lock_t {
/**
Lock which protects the structure.
Also held for the duration of wr-lock.
*/
pthread_mutex_t lock;
/**
Condition variable which is used to wake-up
writers waiting for readers to go away.
*/
pthread_cond_t no_active_readers;
/** Number of active readers. */
uint active_readers;
/** Number of writers waiting for readers to go away. */
uint writers_waiting_readers;
/** Indicates whether there is an active writer. */
my_bool active_writer;
#ifdef SAFE_MUTEX
/** Thread holding wr-lock (for debug purposes only). */
pthread_t writer_thread;
#endif
} rw_pr_lock_t;
由定義可知,rw_pr_lock_t數據結構定義了一個條件變量no_active_readers,用於當所有讀者處理結束后,喚醒寫者獲取寫鎖。定義active_readers變量標示當前讀者數量,定義writers_waiting_readers變量標示當前等待的寫者數量,定義active_writer變量標示當前是否有寫者獲取了鎖。
源碼實現
根據以上表結構定義,分別對兩種結構的基本操作中獲取讀鎖(rdlock)和寫鎖(wrlock)的實現進行分析和說明。
rwlock實現
首先對rwlock的讀鎖rw_rdlock()的實現my_rw_rdlock()(mysys\thr_rwlock.c:224)進行分析。源碼實現如下所示:
int my_rw_rdlock(my_rw_lock_t *rwp)
{
#ifdef _WIN32
if (have_srwlock)
return srw_rdlock(rwp);
#endif
pthread_mutex_lock(&rwp->lock);
/* active or queued writers */
while (( rwp->state < 0 ) || rwp->waiters)
pthread_cond_wait( &rwp->readers, &rwp->lock);
rwp->state++;
pthread_mutex_unlock(&rwp->lock);
return(0);
}
從以上定義中可知,直到沒有寫者並且無寫者等待時,獲取讀鎖,核心實現為函數中重點標示的部分。
rwlock寫鎖rw_wrlock()實現my_rw_rwlock()(mysys\thr_rwlock.c:264)的源碼如下所示。
int my_rw_wrlock(my_rw_lock_t *rwp)
{
#ifdef _WIN32
if (have_srwlock)
return srw_wrlock(rwp);
#endif
pthread_mutex_lock(&rwp->lock);
rwp->waiters++; /* another writer queued */
my_rw_lock_assert_not_write_owner(rwp);
while (rwp->state)
pthread_cond_wait(&rwp->writers, &rwp->lock);
rwp->state = -1;
rwp->waiters--;
#ifdef SAFE_MUTEX
rwp->write_thread= pthread_self();
#endif
pthread_mutex_unlock(&rwp->lock);
return(0);
}
由以上函數實現可知,直到當前沒有讀者時,獲取寫鎖。並且設置為寫者狀態,寫者等待數減少1。核心實現見以上函數中重點標示部分。
從以上實現中可知,讀者和寫者是公平競爭鎖資源的。在mysql的很多場景下,都使用了rwlock,如:權限控制(sql_acl)、插件(sql_plugin)、日志(log)、myisam存儲引擎等子系統。然而,在某些情況下,對讀多寫少的情況下,這種公平競爭鎖資源的實現就顯得不盡人意,如表定義。因此,mysql實現了另一種鎖pr_lock鎖,用於讀優先的多線程控制。
pr_lock實現
根據pr_lock的數據結構定義,對pr_lock的讀鎖rw_pr_rdlock()(mysys\thr_rwlock.c:375)的實現進行分析,源碼如下:
int rw_pr_rdlock(rw_pr_lock_t *rwlock)
{
pthread_mutex_lock(&rwlock->lock);
/*
The fact that we were able to acquire 'lock' mutex means
that there are no active writers and we can acquire rd-lock.
Increment active readers counter to prevent requests for
wr-lock from succeeding and unlock mutex.
*/
rwlock->active_readers++;
pthread_mutex_unlock(&rwlock->lock);
return 0;
}
由以上源碼可知,對於讀優先的鎖來說,可以直接獲取,並且讀者數量增加1。也就是說,讀者可以隨時獲取讀鎖。
pr_lock的寫鎖rw_pr_wrlock()(mysys\thr_rwlock.c:390)的源碼實現如下所示:
int rw_pr_wrlock(rw_pr_lock_t *rwlock)
{
pthread_mutex_lock(&rwlock->lock);
if (rwlock->active_readers != 0)
{
/* There are active readers. We have to wait until they are gone. */
rwlock->writers_waiting_readers++;
while (rwlock->active_readers != 0)
pthread_cond_wait(&rwlock->no_active_readers, &rwlock->lock);
rwlock->writers_waiting_readers--;
}
/*
We own 'lock' mutex so there is no active writers.
Also there are no active readers.
This means that we can grant wr-lock.
Not releasing 'lock' mutex until unlock will block
both requests for rd and wr-locks.
Set 'active_writer' flag to simplify unlock.
Thanks to the fact wr-lock/unlock in the absence of
contention from readers is essentially mutex lock/unlock
with a few simple checks make this rwlock implementation
wr-lock optimized.
*/
rwlock->active_writer= TRUE;
#ifdef SAFE_MUTEX
rwlock->writer_thread= pthread_self();
#endif
return 0;
}
由以上函數可知,直到沒有讀者的情況下,寫者獲取鎖資源。因此,獲取寫鎖時。只有當所有讀鎖都釋放的情況下,寫着才能獲取鎖資源。
從源碼可知,mysql實現的pr_lock主要用於讀優先的情況。通過源碼分析可知,mysql主要用於MDL子系統中。
結論
通過數據結構定義和源碼實現可知,mysql的讀寫鎖不僅有通常的rwlock,用於權限控制(sql_acl)、插件(sql_plugin)、日志(log)、myisam存儲引擎等子系統中。而且實現了pr_lock,用於MDL子系統中。