首页 > 编程知识 正文

互斥信号量中断,实现临界区互斥的软件实现方法

时间:2023-05-03 23:57:26 阅读:150246 作者:650

关键部分:访问(读写)的共享资源的代码称为关键部分。 这里的代码是机器语言的代码,而不是C语言等高级语言的代码。

互斥:当线程处于关键节且正在访问共享资源时,其他线程不会访问相同的共享资源。

锁:保护资源,使外部程序无法访问。 相应的反向操作称为解锁。

死锁:有两个或多个进程,进程之间相互等待对方完成特定任务,导致等待的死循环。

饥饿:可执行进程长期无法获得CPU使用权。

信号量:一些进程只需访问共享资源的属性,与其他进程访问该共享资源的内容不冲突。 采用封锁方式会降低其他任务的执行效率。 信号量是一种粒度小于锁的资源保护机制,常用于共享资源集合的独占访问。

原子操作是指在整个操作过程中中断、不允许上下文切换、一次执行的操作序列。

brddp :高于信号量的抽象。 包含成员变量和函数操作的结构。 其成员包含用于指定临界区域的一个锁,0或多个条件变量用作信号量。 brddp利用3358www.Sina.com/“信号量”管理一组的独占访问

上下文切换计时存在不确定性,对于单个CPU,两个线程同时执行相同的代码。 如果没有合理的同步、互斥机制,可能会得到不同的结果。

共享资源集合

一.临界区同步和互斥机制

1 )禁用硬件中断

2 )两个线程之间的临界区域访问——Dekker算法

3 ) n线程之间的临界区域访问——Bakery算法

4 )基于锁的原子操作Test and set方法

4 )基于锁的原子操作Exchange方法

二、基于信号量的同步互斥机制

1 )生产者和消费者

2 )信号量的实现

三. brddp

1)条件变量的实现

2)生产者和消费者

3)Hansen Style 和 Hoare Style


一、临界区的同步和互斥机制

     临界区

1)禁用硬件中断

     此方法采用屏蔽硬件中断的方法,让临界区的代码不会被中断,不会被上下文切换,自然也就没有了并发问题。但这只适用于单CPU的系统。而对于多CPU的系统还是无法解决互斥问题的。

2)两个线程间的临界区访问——Dekker算法

   此算法需要两个共享的全局变量 turn 和flag 数组,分别表示临界区该被哪一个线程访问  和 表示 哪一个线程想要访问临界区,需给予默认值。其中 i 表示自己的线程标识,j表示另外一个线程的标识。这个算法的原理是:当有两个线程需要访问临界区时,各自发出访问请求 flag[i] = ture ,turn相当于临界区属性,只有进入临界区才可以修改turn标志。而允许进入临界区的条件是当turn的值为自身编号,并且flag [i]为ture时才行。

 

3)N线程间的临界区访问——Bakery 算法

     当由N个进程进入临界区时,每个进程接收到一个数字,得到数字最小的进程进入临界区,如果拿到的数字相同,则进程号pid小的先进入临界区去执行代码。

 

4)基于锁的原子操作Test and set 方法

    利用锁访问的方式。其lock的属性value默认值为0,代表未被锁。有原子方法TestAndSet  方法,这方法执行时不允许被中断。当请求锁方法种,执行testAndSet,如果发现返回值是0,代表可访问,在TestAndSet 方法种自动将value设置成1,任务执行完后把value重新置成0。当任务在执行过程中,其他进程也需要访问锁时,testAndSet返回值为1,进入忙等循环或者阻塞。

4)基于锁的原子操作Exchange方法

     利用锁和两个内存单元来完成临界区的访问。这两个内存单元的值分别是0 和1。有原子方法Exchange方法,这方法执行时不允许被中断。其原理是利用两个内存单元lock 和 key,lock是全局变量,Key是局部变量,当需要访问时将Key设置成1,用Exchange 的方式来交换lock值,来代表获取锁资源。执行完后再将锁资源置成0.

 

二、基于信号量的同步互斥机制

   信号量常用于共享资源集合的访问。 信号量的操作只有两种,V()方法代表信号量增加,释放资源。P()方法代表信号量减少,获取资源,当无资源可获取时阻塞线程。信号量是个有符号的整数,是全局变量,初始化值通常等于可访问资源集合的资源数。当信号量的取值范围要求在[0,1]范围内,相当于锁。

 

1)生产者和消费者

     使可访问的共享资源数增多的任务称为生产者,使可访问的共享资源数减少的任务称为消费者。对于锁的应用,刚开始可访问资源为1,只能允许有1个消费者,当消费资源后,由自身去释放资源,所以自身又是生产者。又例如有一群任务A专门生成文件,又有一群任务B专门读取并删除文件,这个也是生产者消费者模式。这个模式要求:生产者可以多个,当缓存区满时不允许再生产,消费者可以多个,当缓存区空时不再允许消费。缓存区在同一个时刻最多只能一个线程去访问、操作。

    mutex信号量初始值1,用来实现控制缓存区的访问互斥。 fullBuffers 用来控制消费者的可消费资源数。emptyBuffers 用来控制生产者的可生产资源数。

其中缓存区的互斥访问P必须放在最后,由于P操作会造成线程阻塞,如果信号量P()操作顺序不正确,可能会发生死锁。通常的原则是访问越早的资源,其信号量应越晚去P(),由于任何一个任务要先访问缓存区,再去访问缓存区中的资源,所以缓存区自身的信号量mutex 要比 emptyBuffers 和 fullBuffers更晚去 P().

例如将Deposit(c)的emptyBuffers->P() 和 mutex->P()的顺序进行交换。  当emptyBuffers 值为0,FullBuffer值为n 时,生产者A执行Deposit 执行mutex->P()   和emptyBuffers->P()造成阻塞,另消费者B执行FullBuffers->P() 和mutex->P() 也造成阻塞,相互等待对方任务执行V()操作造成死锁。

例如将Deposit(c)的emptyBuffers->P() 和 mutex->P()的顺序进行交换,也将Remove(c)的fullBuffers->P() 和 mutex->P()的顺序进行交换。 当emptyBuffers 值为0,FullBuffer值为n 时,生产者A执行Deposit 执行mutex->P()   和emptyBuffers->P()造成阻塞, 消费者B执行mutex->P() 也造成阻塞,相互等待对方任务执行V()操作造成死锁。

 

2)信号量的实现

     所需要实现的方法是P() 和V()操作,这两个操作方法是原子指令且必须禁用中断。需要考虑的实现内容是:如何阻塞线程,又如何唤起线程。通常需要一个等待队列用来存 因信号量不足而阻塞的线程。

当sem为负数时代表被阻塞的线程数,sem>=0 时表示可访问资源数。 P操作将信号量减1,并判断原先的资源数是否大于0,如果原先的资源数不足就将此线程加入(等待)阻塞队列。 V操作将信号量加1,并判断原先的资源数是否大于0,如果原先的资源数不足就把刚释放出的资源提供给被阻塞的线程。用队列进行管理阻塞态的线程,先被阻塞,先获取资源。

 

三、brddp

   brddp中包含锁,锁用来控制线程的互斥访问,即在某一时刻brddp最多只能被一个线程访问。用队列entryQueue来管理待访问brddp的线程。brddp中包含0个或多个条件变量。这些条件变量相当于信号量集合,用来共同控制共享资源的同步访问。每个信号量利用队列来保存因资源不满足而被阻塞的线程。

1)条件变量的实现

     条件变量类似于信号量它的实现逻辑和信号量类似。但是也有所区别,其中信号量的int类型变量用来记录剩余资源数。而brddp条件变量用于记录阻塞线程数,所以当操作时与信号量的操作顺序相反。

条件变量调用wait方法时,让阻塞线程数加1后把线程加入阻塞队列,释放brddp的锁并sechedule()阻塞线程让出CPU使用权,阻塞完线程后再获取锁,不让其他外部线程访问brddp。条件变量之所以先释放锁后获取锁,原因是在入wait方法之前brddp就已经被锁上了。让出CPU使用权时,必须释放锁。不然阻塞线程由于资源不足无法唤醒,锁没释放导致外部线程无法访问brddp,那么brddp将会无法运行。

条件变量调用signal方法时,如果当前有阻塞线程,把阻塞线程唤醒,阻塞线程数减1.

2)生产者和消费者

       count 用来表示共享资源个数,notFull为可生产剩余资源数的条件变量,notEmpty为可消费剩余资源数的条件变量。

       当线程需要生产资源时,必须先请求锁,如果缓存区则进入阻塞态让出CPU使用权,停止执行该线程的代码。如果该线程被唤醒进入就绪态,重新获取CPU使用权,那么将继续执行原先Condition::Wait方法中的require(lock)。如果当前资源数还是的那么循环继续阻塞,如果没满就生产出了一个单位的资源,唤醒需要消费该资源的阻塞线程。

       当线程需要消费资源时,必须先请求锁,如果缓存区则进入阻塞态让出CPU使用权,停止执行该线程的代码。如果该线程被唤醒进入就绪态,重新获取CPU使用权,那么将继续执行原先Condition::Wait方法中的require(lock)。如果当前资源数还是的那么循环继续阻塞,如果没空就消费掉了一个单位的资源。唤醒需要生产该资源的阻塞线程。

3)Hansen Style 和 Hoare Style

    Hansen和Hoare 是计算机科学家,各提出了一个方法用来解决brddp的问题:当brddp中执行的线程A发出Signal操作后,意味这需要该资源的阻塞线程B会被唤醒。那么此时A和B线程都在brddp中处于就绪态,那么A和B都有可能在brddp中执行,这就不满足互斥条件了。

    Hoare提出的思想是在Signal()的时候唤醒因资源不足被阻塞的线程让它先执行,即在资源紧张的情况下,产出资源将立马被消费。。 而Hansen提出:当A发出signal操作时,唤醒等待(阻塞)线程B,先执行完自身A后再并让B执行完。文章上面举的例子都是Hansen模式。 Hansen 和 Hoare在signal()方法上有所区别。 Hansen的Signal一口气执行完。而 Hoare的Signal唤醒阻塞线程后阻塞自己,释放锁让出CPU使用权

   Hoare模式生产者消费者问题:

       当线程需要生产资源时,必须先请求锁,如果缓存区满则进入阻塞态让出CPU使用权,停止执行该线程的代码。如果有消费者signal(),则该线程被唤醒进入就绪态,重新获取CPU使用权,那么将生产出了一个单位的资源,唤醒需要消费该资源的阻塞线程,阻塞自己并交出自己的CPU使用权,等待消费者线程唤醒自己,此时CPU重新回到自己手里意味着执行完毕。

       当线程需要消费资源时,必须先请求锁,如果缓存区空则进入阻塞态让出CPU使用权,停止执行该线程的代码。如果有生产者signal(),则该线程被唤醒进入就绪态,重新获取CPU使用权,那么将消费掉了一个单位的资源。唤醒需要生产该资源的阻塞线程。阻塞自己并交出自己的CPU使用权,等待生产者线程唤醒自己,此时CPU重新回到自己手里意味着执行完毕。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。