信号量 semaphore
信号量是操作系统提供的一种协调共享资源访问的方法
软件同步是平等线程间的一种同步协商机制
OS是管理者,地位高于进程
用信号量表示系统资源的数量
由一个整形 (sem)变量和两个原子操作组成
P()(Prolaag (荷兰语尝试减少))
sem减1
如sem<0, 进入等待, 否则继续
V()(Verhoog (荷兰语增加))
sem加1
如sem≤0,唤醒一个等待进程
P() 可能阻塞,V()不会阻塞,这些只能在内核实现,而不能在用户实现。
信号量的使用:
(1) 同步 线程间的事件等待
(2) 互斥 临界区的互斥访问控制
用信号量实现临界区的互斥访问必须成对使用P,V操作 ,不能次序错误、重复或者遗漏
条件同步:初值设为0
自旋锁不能实现先进先出:
它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。
跟互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。由此我们可以看出,自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁可能存在两个问题:
死锁。试图递归地获得自旋锁必然会引起死锁:递归程序的持有实例在第二个实例循环,以试图获得相同自旋锁时,不会释放此自旋锁。在递归程序中使用自旋锁应遵守下列策略:递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。此外如果一个进程已经将资源锁定,那么,即使其它申请这个资源的进程不停地疯狂“自旋”,也无法获得资源,从而进入死循环。
过多占用cpu资源。如果不加限制,由于申请者一直在循环等待,因此自旋锁在锁定的时候,如果不成功,不会睡眠,会持续的尝试,单cpu的时候自旋锁会让其它process动不了. 因此,一般自旋锁实现会有一个参数限定最多持续尝试次数. 超出后, 自旋锁放弃当前time slice. 等下一次机会
由此可见,自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP(多处理器)的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作,
与前面的区别 P,V代码的原子性由操作系统保护。
必须成对使用P,V
如果不申请(P操作),直接V操作:就会有多个线程进入到临界区
如果申请了(P不操作),不释放(V):谁都不能进入临界区
不能避免死锁的出现,必须在写程序的时候解决这个问题。
sim++:(保证操作正确性)
TestAndSet(&lock),等价exchange
中断
软件处理
* 实现条件同步:线程A在线程B后执行
信号量初始值:0
信号量解决生产者,消费者问题
emptyBuffers->P()
mutex->P(); 两者交换,程序出现死锁
mutex-V()
fullBuffers->V()两者交换顺序,对程序正确性没有任何影响。
第二种同步方法:管程
信号量:P,V 操作分配在生产者与消费者中,P,V的配对是比较困难的。
信号量使用锁,管程使用条件变量,在解决同步互斥问题上,信号量与管程等价。
管程:P,V操作的配对,集中在一起
(1)管程采用面向对象的方法,简化了线程间的同步控制。
(2)任何时刻只有一个线程在执行管程代码
(3)临界区也是任何时刻只有一个线程在执行管程代码,但是他们有区别,区别是:正在管程中的线程可临时放弃管程的互斥访问,等待事件出现时恢复。
管程的使用:
在对象、模块中收集相关共享的数据
定义访问共享数据的方法,在其他地方不需要同步互斥操作了。
管程也可能出现死锁的情况
管程中的局部变量不可以被外部直接访问
管成组成:
一个锁(入口,出口)
控制管程代码的互斥访问
0或者多个条件变量
管理共享数据的并发访问
临界区只有一个条件变量,而管程管理一系列的条件变量
条件变量
条件变量是管程内的等待机制
进入管程的线程因资源被占用而进入等待状态
每个条件变量表示一种等待原因,对应一个等待队列
Wait()操作
将自己阻塞在等待队列中
唤醒一个等待者或释放管程的互斥访问
与信号量区别:不用判断sim/numWaiting是否为空,直接加入waitQueue 中,而且不用管配对的问题。
Signal()操作
将等待队列中的一个线程唤醒
如果等待队列为空,则等同空操作
用管程解决生产者-消费者问题
效率不如信号量的效率
读者、写者问题:
规则:“读-读“允许 “写-读”互斥” ‘ “写-写”互斥
管程实现读者-写者问题
AR=0 Active readers 当前正在读的读者
AW=0 当前正在写的写者
WR=0 当前等待的读者
WW=0 当前等待的写者
读者-写者问题 (考试实现读者优先),
读者优先
写者端: StrarWrite() while(while(AR+WR>0)){okToWrite.wait()}
DoneWrite() if(AW==0&&WR>0){okToRead.signal()}
读者端:
StartRead() while(AW+AR>0){okToRead.wait()}
DoneRead() if(WR>0){okToRead.signal()}else if(WW>0){okToWrite.signal()}
写者优先
读者端:
stratRead()函数:while(AW+WW>0){ okToReda.wait()}
DoneRead()函数: if(AR==0&&WW>0) //没有读者且有写者等着则释放锁
oktowrite.signal()
写者端:
startWrite() while((AW+AR)>0){ okToWrite.wait} 但是有等待读的读者,是不等的,则说明是写者优先的
DoneWrite if(WW>0){okToWrite.signal()}else if(WR>0){okToRead.broadcast()}