信号量
P(): sem 减一
V():sem+1
-
一个是 P 操作,这个操作会把信号量减去 1,相减后如果信号量 < 0,则表明资源已被占用,进程需阻塞等待;相减后如果信号量 >= 0,则表明还有资源可使用,进程可正常继续执行。
-
另一个是 V 操作,这个操作会把信号量加上 1,相加后如果信号量 0,则表明当前没有阻塞中的进程;
P 操作是用在进入共享资源之前,V 操作是用在离开共享资源之后,这两个操作是必须成对出现的。
接下来,举个例子,如果要使得两个进程互斥访问共享内存,我们可以初始化信号量为 1
。
具体的过程如下:
-
进程 A 在访问共享内存前,先执行了 P 操作,由于信号量的初始值为 1,故在进程 A 执行 P 操作后信号量变为 0,表示共享资源可用,于是进程 A 就可以访问共享内存。
-
若此时,进程 B 也想访问共享内存,执行了 P 操作,结果信号量变为了 -1,这就意味着临界资源已被占用,因此进程 B 被阻塞。
-
直到进程 A 访问完共享内存,才会执行 V 操作,使得信号量恢复为 0,接着就会唤醒阻塞中的线程 B,使得进程 B 可以访问共享内存,最后完成共享内存的访问后,执行 V 操作,使信号量恢复到初始值 1。
可以发现,信号初始化为 1
,就代表着是互斥信号量,它可以保证共享内存在任何时刻只有一个进程在访问,这就很好的保护了共享内存。
另外,在多进程里,每个进程并不一定是顺序执行的,它们基本是以各自独立的、不可预知的速度向前推进,但有时候我们又希望多个进程能密切合作,以实现一个共同的任务。
例如,进程 A 是负责生产数据,而进程 B 是负责读取数据,这两个进程是相互合作、相互依赖的,进程 A 必须先生产了数据,进程 B 才能读取到数据,所以执行是有前后顺序的。
那么这时候,就可以用信号量来实现多进程同步的方式,我们可以初始化信号量为 0
。
具体过程:
-
如果进程 B 比进程 A 先执行了,那么执行到 P 操作时,由于信号量初始值为 0,故信号量会变为 -1,表示进程 A 还没生产数据,于是进程 B 就阻塞等待;
-
接着,当进程 A 生产完数据后,执行了 V 操作,就会使得信号量变为 0,于是就会唤醒阻塞在 P 操作的进程 B;
-
最后,进程 B 被唤醒后,意味着进程 A 已经生产了数据,于是进程 B 就可以正常读取数据了。
可以发现,信号初始化为 0
,就代表着是同步信号量,它可以保证进程 A 应在进程 B 之前执行。
初始值可以设置为0,当火车进入之前,执行p-操作,然后走了之后,执行v+操作,使的后面的阻塞进程被告知可以进入
生产者消费者
临界资源mutex设置为1,空资源空间设置为n,满空间为0
流程是
1.生产者先把empty--;然后进去临界区,mutex 为0,可以进入,但是消费者不能干预,所以添加数据,退出临界区的时候,mutex+1,使得消费者可以进入,然后full+1,表示增加了一个资源
2.消费者首先把full-1,然后进入临界资源,将数据取出,然后退出临界资源,empty+1;
管程
生产者消费,进去直接锁,互斥
发出唤醒的线程和被唤醒的线程谁执行?
1.第一个睡眠,被唤醒的直接占用cpu执行
2.第一个执行完relase lock 才给第二个被唤醒的线程
当一个线程在管程中等待某个条件时,它释放对管程的锁定并进入等待状态。这时,管程中的其他线程可以继续执行。当条件满足时,需要唤醒等待的线程。管程中的两种常见唤醒线程的方法如下:
方法一:被唤醒的线程直接占用 CPU 执行
-
线程 A 进入管程并等待某个条件,例如等待一个共享资源可用。
-
线程 B 获得管程的锁定并修改了共享资源,然后通知条件满足。
-
管程唤醒等待的线程 A。
-
线程 A 被唤醒后重新获得管程的锁定,并开始执行。它会检查条件是否满足,如果满足,则继续执行相关操作,否则可能再次进入等待状态。
方法二:等待的线程等待第一个线程释放锁定后才被唤醒
-
线程 A 进入管程并等待某个条件,释放管程的锁定并进入等待状态。
-
线程 B 获得管程的锁定,并修改了共享资源,然后通知条件满足。
-
管程唤醒等待的线程 A,但线程 A 不立即执行。
-
线程 B 完成对共享资源的修改后,释放管程的锁定。
-
被唤醒的线程 A 获得管程的锁定并开始执行。它会检查条件是否满足,如果满足,则继续执行相关操作,否则可能再次进入等待状态。
在方法二中,被唤醒的线程不会立即执行,而是等待第一个线程释放锁定后才被唤醒并获得锁定。这样可以避免不必要的上下文切换和竞争,但可能会导致一些等待时间。具体使用哪种方法取决于编程语言、操作系统和具体的应用场景。