互斥量实现

互斥量是一个可以处于两态之一的变量:加锁和解锁,这意味着一个二进制位就可以表示他,但在实现中,常常使用一个整型变量,0表示解锁,而其他所有的值表示加锁。互斥量使用两个过程。

1.当一个线程或者进程想要访问临界区时,它调用mutex_lock。如果该互斥量当前是解锁的,此调用成功,该线程进入临界区。另外,如果该互斥量已经加锁,调用线程将被阻塞,直到持有该锁的线程释放锁。如果多个线程阻塞在该互斥量上,将随机选择一个线程并允许它获得锁。

2.当线程要退出临界区时,调用mutex_unlock。

两过程代码如下

mutex_lock:
    TSL REGISTER,MUTEX    //将互斥信号量复制到寄存器,并且将互斥量置为1
    CMP REGISTER,#0       //互斥量是否为0
    JZE OK                //互斥量为0,此锁是释放状态,该线程可以进入互斥区
    CALL thread_yield     //该锁被其他线程持有,调度另一个线程
    JMP mutex_lock        //稍后重试
ok:RET                    //返回调用线程,进入临界区

mutex_unlock:
    MOVE MUTEX,#0         //释放锁
    RET                   //返回

指令相当简单,介绍两点

1.TSL指令:称为测试并加锁(test and set lock),它将一个内存字lock读到寄存器中,然后在该内存地址上存一个非零值。执行TSL指令的cpu将锁住内存总线,以禁止其他CPU在本指令结束前访问内存,这就防止了竞争条件(当某个过程的结果取决于逻辑执行流的调度顺序时,即存在竞争条件)     注意:锁住内存总线不同于屏蔽中断。屏蔽中断是指进程在刚刚进入临界区时屏蔽所有中断,这其中就包括了时钟中断,而CPU只有发送时钟中断或其他中断时才会进行进程切换。这就保证了,在单处理器系统中,屏蔽中断就可以实现互斥。但是在多处理器中,屏蔽中断并不管用,其他CPU仍然可以进入共享内存。因此引入了锁住内存总线,这样其他CPU也不能访问内存。

2.CALL thread_yield这个指令妙处在于不用通过忙等待浪费CPU,而去将CPU分配给另一个线程。当该线程下次分得时间片运行时,再一次对锁进行测试。

另外要提的是

快速用户区互斥量futex

随着并行的增加,有效的同步和锁机制对性能而言非常重要。如果等待时间短的话,完全可以用自旋锁忙等待,因为如果阻塞进程,陷入内核的开销远远比其大。但如果等待时间长,则会浪费CPU周期。如果有很多竞争,那么阻塞该进程,并仅当锁被释放的时候让内核接触阻塞会更加有效。然而,这却带来了相反的问题:竞争不激烈时,那么不断地内核切换将花销太大。

因此引入了futex,他是linux系统的一个特性,结合了以上两者之所长。它实现了基本的锁,但避免陷入内核,除非它是真的不得不这样做。因为来回切换到内核花销很大,所以这样做可以十分可观的改善性能。一个futex包含两个部分:一个内核服务和一个用户库。内核服务提供一个等待队列,它允许多个进程在一个锁上等待,直到内核对他们解除阻塞。将一个进程放到内核等待队列需要系统调用,而系统调用将陷入内核,这会非常花费时间,我们应该尽量避免他们。因此,在没有竞争时,futex完全在用户空间工作,进程共享一个锁变量,假设锁初始态是释放状态,初始值为1.线程要进入临界区前,通过执行原子操作"减少并检验"来获得锁,接下来,这个线程检查结果,看锁是否被释放,如果为处于被锁状态,线程成功获取锁。如果该锁被其他线程持有,则进行系统调用将该线程投入内核等待队列。当一个线程将要退出临界区时,进行原子操作"增加并检验"来释放锁,并检查结果,看是否扔有进程阻塞在内核等待队列上。如果有,通知内核可以对等待队列里的一个或个线程解除阻塞。


参考文献:《现代操作系统》

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值