在臨界區的排程原則中有:在實際應用中,我們考慮對臨界區的管理有軟體算法,也有硬體設施,但是這些偏軟,偏硬的方法,或存在複雜、效率低下,或存在浪費CPU時間等問題。下面筆者将和大家談談一種新的同步工具:信号量和PV操作。
- 互斥使用
- 有空讓進
- 忙則等待
- 有限等待
- 擇一而入
- 算法可行
PV操作
PV操作是屬于原語操作,原語操作即是執行時是不可被打斷的,如原子一般不可再分,通過PV操作我們可以保證執行時不可打斷且信号量值的完整性。
-
:将信号量的值減一(P(s)
)s.value - 1
- 如果結果小于0(
),執行P操作的程序将被阻塞,放入到與s信号量有關的list所指向的隊列中s.value < 0
- 如果結果大于0(
),則執行P操作的程序繼續執行。s.value>=0
- 如果結果小于0(
-
:将信号量的值加一(V(s)
)s.value+1
- 若結果不大于0(
),則執行V操作的程序從與信号量有關的list所指向的隊列中釋放一個程序,使其轉于就緒态,自己繼續執行;s.value <= 0
- 若結果大于0(
),則執行V操作的程序繼續執行。s.value>0
- 若信号量
為正值,此值等于在封鎖之前對信号量s可施行的P操作數,即s.value
所代表的實際可用的實體資源數。s
- 若信号量
為負值,其絕對值等于登記排列在s信号量隊列之中等待的程序個數,即恰好等于對信号量s.value
實施P操作而被封鎖的并進入信号量s
等待隊列的程序數s
- 通常P操作意味着請求一個資源,V操作意味着釋放一個資源,在一定條件下,P操作代表挂起程序的操作,V操作代表被喚醒被挂起的程序的操作。
- 若結果不大于0(
信号量
在作業系統中将用信号量來表示實體資源的實體,與隊列有關。同時,須知:
信号量是一種變量類型,有如下兩個分量:
- 信号量的值(value)
- value>0,表示實際可用資源數
- value=0,表示資源為0
- value<0,表示正在等待資源的程序數
- 信号量的指針:用于指向等待的程序
按用途來分,信号量分為兩種:
- 公用信号量:
- 聯系一組并發程序,相關程序可以在此信号量上做PV操作,初值為1,這個為1,是為了實作程序的一個互斥;
- 私有信号量:
- 聯系一組并發程序,僅允許此信号量所擁有的程序執行P操作,其他相關的程序可實施V操作,初值一般為0或正整數,在程序同步中常用。
按取值來分,又有如下兩種:
- 二值信号量
- 僅能取值為0或1,解決程序互斥
- 一般信号量
- 允許取值大于1,常用于解決程序同步。
信号量的資料結構以及PV操作的細節
typedef struct semaphore
{
int value; //信号量的值
struct pcb * list;//信号量的隊列指針
}
void P(semaphore s){
s.value --;
if(s.value < 0) // 小于0 被阻塞
sleep(s.list);
}
void V(semaphore s){
s.value ++;
if(s.value <= 0)
wakeup(s.list); //小于等于0 從信号量隊列中釋放一個等待的程序,并轉為就緒态
}
信号量實作互斥
通過使用信号量和PV操作來管理并發程序進入臨界區的形式:
semaphore mutex;
mutex=1;
cobegin
process Pi(){
P(mutex);
/**臨界區*/
V(mutex);
}
當有程序在臨界區中時,mutex的值為0或者負值,否則mutex為1.因為隻能有一個程序的P操作能把mutex的值減0,進而能夠保證互斥操作
實際應用(C語言實作)
現在有以下場景:
有一家人,小明專門吃蘋果,小紅專門吃橘子,爸爸放蘋果,媽媽放橘子,盤子上面隻能有一個水果,而隻有相對應的水果孩子們(孩子專門吃其對應的水果)才能拿來吃,隻有當盤子空的時候,爸爸媽媽才能放水果,試用程序并發實作這個操作。
首先要思考的是,臨界資源是什麼?顯然,是盤子,盤子有多少種狀态?三種!分别是:
- 盤子是空的
- 盤子上有橘子
- 盤子上有蘋果
當有多個程序并發時,我們要保證資源區在每時每刻都能保證:互斥使用、有空讓進、忙則等待、有限等待、擇一而入,算法可行。下面我将使用PV操作實作這個操作:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include<semaphore.h>
#define P sem_wait
#define V sem_post
#define orange &orange_
#define apple &apple_
#define empty &empty_real1
sem_t orange_;
sem_t apple_;
sem_t empty_real1;
int num=0;
void * mother(){
while(num < 30)
{
P(empty);
num++;
printf("媽媽放橘子 %d\n",num);
V(orange);
}
}
void * father()
{
while(num < 30)
{
P(empty);
num++;
printf("爸爸放蘋果 %d\n",num);
V(apple);
}
}
void * xiaoming()
{
while(num < 30)
{
P(apple);
num++;
printf("小明吃蘋果 %d\n",num);
V(empty);
}
}
void * xiaohong(){
while(num < 30)
{
P(orange);
num++;
printf("小紅吃橘子 %d\n",num);
V(empty);
}
}
int main()
{
sem_init(apple,0,0); //初始化信号量的值為0,并且設定其為程序内的線程共享
sem_init(orange,0,0);
sem_init(empty,0,1); //初始信号量的值為1,表示剛開始的時候盤子是空的,資源可用
pthread_t tid1;
pthread_t tid2;
pthread_t tid3;
pthread_t tid4;
pthread_create(&tid1,NULL,mother,NULL);
pthread_create(&tid2,NULL,father,NULL);
pthread_create(&tid3,NULL,xiaoming,NULL);
pthread_create(&tid4,NULL,xiaohong,NULL);
pthread_exit(0);
return 0;
}
以上代碼的執行結果:

這裡可能大家看到為啥
num
的值為啥會大于30,這時思考以下
num
的範圍,并且線上程等待的時候,是否
num
值已經被判斷過了了?即可得知答案。
好了相關内容就分享到這裡,如果你覺得有用的話可以給我點個贊,謝謝!