阿里巴巴DBA,原去哪儿网DBA。专注于MySQL源码研究、DBA运维、CGroup虚拟化及Linux Kernel源码研究等。 github:https://github.com/HengWang/ Email:king_wangheng@163.com 微博 :@王恒-Henry QQ :506437736
分类: Mysql/postgreSQL
2012-10-22 23:22:42
目的
在源码分析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. */ typedef struct st_my_rw_lock_t { pthread_mutex_t lock; /* lock for structure */ pthread_cond_t readers; /* waiting readers */ pthread_cond_t writers; /* waiting writers */ int state; /* -1:writer,0:free,>0:readers */ int waiters; /* number of waiting writers */ #ifdef SAFE_MUTEX pthread_t write_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子系统中。