mysql互斥_MySql中互斥量mutex的实现
数据库中的Mutex量指的是⼀种⽤于保护⼀些临界资源的使⽤的信号量。当有线程需要使⽤这些临界资源时,会请求获得mutex量,请求成功的线程进⼊临界区,⽽请求失败的线程只能等待它释放这个mutex。互斥信号量在计算机软件层⾯以上可以看作是实现并发操作的⼀个原⼦动作,但在数据库(操作系统)这种⾼并发多线程的基础软件中,需要精⼼设计以获得⾼吞吐量和良好响应时间。
Mysql中的mutex实现机制⼤致描述如下(基于Windows操作系统):
数据结构:在mutex结构中,有:
Ø        event:操作系统提供的信号量;
Ø        lock_word:锁,这是⼀个机器字长的数,为1说明mutex有⼈使⽤,为0则没有。
Ø        waiters:标志有⽆⼈在等待这个mutex,同样是机器字长的数
Ø        list:⼀个list把系统所有的mutex连起来,这⾥不⽤关注
Ø        其它⼀些debugInfo,不⽤关注。
同时MySql维护⼀个全局等待队列sync_primary_wait_array,在当中有:
Ø        array:⼀个cell数组,每个cell中是⼀个等待对象的描述,包括等待类型request_type,等待对象指针object、等待线程
ID,waiting布尔量表⽰是否在等待,这个数组在全局等待队列sync_primary_wait_array初始化时被初始化为系统最⼤线程数⽬⼤⼩。
Ø        os_mutex:⼀个操作系统提供的互斥量,使⽤它保护这个全局等待队列。
mysql下载失败怎么办Ø        其它信息,不⽤关注。
当⼀个线程开始请求⼀个mutex,它按照以下步骤来进⾏:
1)TestAndSet,使⽤⼀条汇编指令来尝试获得这个mutex,这个动作是计算机原⼦的。
2)如果操作成功,则mutex中的lock_word被置成1,本线程返回,可以进⾏临界区操作。
3)如果失败,开始⼀个spinLock的过程(所谓spin就是指去扭转⼀个门把⼿,如果扭不开把⼿会转回来,然后就继续去扭直到它开为⽌,很形象的⼀个过程,呵呵),不停循环并且读lock_ward的值,发
现为0了(注意,是脏读,所以去TestAndSet仍然可能失败)则再次去执⾏TestAndSet。若置位成功了,则返回。
4)spinLock循环到了⼀定次数,不能继续spin下去了,否则太消耗CPU资源,此时本线程睡眠,醒来后再尝试⼀次TestAndSet,若成功了,则返回。
5)进⼊全局等待队列sync_primary_wait_array中,通过队列中的os_mutex保证此时在队列中的操作是唯⼀的。到⼀个空的Cell,把它的对象指针object指向⾃⼰,等待线程ID设为⾃⼰的ID号,request_type设置为MUTEX,waiting设为FALSE,同时调⽤操作系统接⼝reset互斥量mutex上的event(API: ResetEvent),退出全局等待队列。
6)把mutex中的waiters设为1,标志有⼈在等待这个mutex,这个动作使⽤了⼀个volatile 的指针来改这个值,以保证这个值被⽴即写回内存。
7)这时再尝试去做⼏次TestAndSet(垂死挣扎^_^),若成功了,回去把全局等待队列sync_primary_wait_array中⾃⼰刚拿到的cell清空掉,并返回。
8)进⼊全局等待队列sync_primary_wait_array,把⾃⼰的cell⾥的waiting标识为TRUE,拿到event后退出全局队列。
9)等待在这个event上直到被唤醒(API:WaitForSingleObject),被唤醒后⽴即进⼊全局等待队列⾥放掉⾃⼰的cell,再继续从1)开始。
接下来是线程释放mutex的步骤:
1)ResetLock,同样使⽤⼀条汇编指令将mutex中的lock_word设置为0;
2)检查mutex上的waiters是否为1,如果是,则执⾏下⾯3)4)两步的唤醒例程,否则返回。
3)先将waiters设置为0,这同样使⽤⼀个volatile的指针来做。
4)将mutex上的event唤醒(API:SetEvent)。
分析:
MySql的mutex实现依据如下思想:先反复尝试⼀⼩段时间(spin);若不成功,不能继续⽆⽌境地浪费CPU,只好进⼊休眠;休眠⼀段时间醒来还是获得不了,则只好等待释放mutex的⼈来将⾃⼰唤醒。Mutex的实现需要⼩⼼两种情况:1是出现mutex保护失败,两个线程同时获得了临界区。在这套实现机制中,明显在spin和Sleep的过程中获得的mutex是安全的,因为它们是通过汇编原语来得到的mutex,⽽⼀旦进⼊event等待再被唤醒的⼈也必须重新使⽤TestAndSet来获得mutex,所以这种情况
是不可能的。2是某个线程被永远“遗忘”在获得mutex的过程内,成为了死线程。在spin和sleep的过程中是不可能的,因为线程会主动醒来不断尝试。⽽线程如果进⼊了event等待,那么它也⼀定会在将来被另⼀个线程唤醒。(但是请注意,这是按我们上⾯的序列来执⾏的理论情况,⼈真的能相信计算机的⾏为吗?后⾯会有答案的)下⾯先证明这点:
在⼀个线程A进⼊event等待前,它做了这样的步骤:
1.        resetEvent
2.        将waiter置为1
3.        再去尝试⼏下
4.        等待在event上
在这个过程结束后,是不可能出现没有其它任何线程在持有这个mutex⽽event仍然不是signal状态的。因为线程A在将waiter置为1后⼜去尝试了⼏次,这时仍然是得不到的(垂死挣扎是关键1)!明显这时候肯定还有⼀个线程B在持有这个mutex,⽽这个线程B在结束的时候,是不可能不看到这个waiter为1的(因为waiter的修改是⽤了volatile的声明指针)。这时这个线程B肯定要去调⽤⼀次setEvent(关键2),从⽽保证了A线程不可能被“遗忘”。
但是线程在看到waiter为1的时候,是不能保证确实有其它线程在等待event的。考虑这样⼀个序列:
Ø        ⼀个线程A请求获得mutex,在到达步骤6之前,mutex被B持有。
Ø        在A执⾏6之前的时间,B释放了mutex,这时B读到的waiters还是0,B直接返回。
Ø        A把waiters设置为1,然后再去垂死挣扎做TestAndSet,这时由于B已经释放了mutex,A成功,A放掉⾃⼰的cell并返回。这时waiter的值为1,⽽mutex上是没有⼈在等待这个event的。当A放mutex的时候,会去唤醒event,但这没有什么问题,因为当线程发现⾃⼰有可能要等待的时候,是会重新在步骤5)⾥⾯resetEvent的。
看起来这个mutex是安全的了吧?错!这个序列⾏为理论上是安全的,但计算机可未必⼀定按你想像的去做…在MySql的源代码中就有⼀段说明,众所周知,在超线程的CPU上指令的执⾏是可能乱序执⾏的,只要这个乱序⾏为的指令不具有代码相关性。在释放mutex的步骤中的1)和2),这两条指令就没有代码相关性,如果在做1)的动作前2)超前被执⾏了的话,上⾯所有的假设都成了⽔⽉镜花……这时候考虑⼀个序列:
Ø        ⼀个线程A持有着mutex,另⼀个线程B开始申请。
Ø        在B执⾏6之前,A先执⾏了释放步骤中的2),此时waiters是0,这时A的时间⽚到了,A被切换。
Ø        B⼀路执⾏下去,由于A没有resetLock,B是⽆法成功了,只好去等待在event上
Ø        A回来resetLock,它的堆栈现场⾥waiters还是0,A潇洒的离开了。于是B就悲剧地永远等在了event上,如果再也没有其它线程来申请这个mutex的话。
为了避免这个情况,MySql只得使⽤后台线程来定期检查这个全局队列(其实这也是全局队列需要存在的必要原因),发现有被“遗忘”的线程则去唤醒它。
可见数据库中的mutex是⼀个需要考虑硬件实现,并且要考虑多种资源的平衡因素的关键性数据结构。但MySql的实现个⼈认为过于复杂了,Mutex作为⼀个可以看作是数据库级别的最轻量级并发控制器,不应该被考虑⽤于平均时间过长的临界区。性能才是mutex应该衡量的最关键因素。
觉得⽂章有⽤?⽴即:
和朋友⼀起 共学习 共进步!
猜想失败,您看看下⾯的⽂章有⽤吗?

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。