目录
在计算机操作系统中,PV操作是进程管理中的难点。
1、基本含义
什么是信号量?信号量(semaphore)的数据结构为一个值和一个指针,指针指向等待该信号量的下一个进程。信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量;当它的值小于0时,其绝对值表示等待使用该资源的进程个数。
PV操作的含义:PV操作由P操作原语和V操作原语组成(原语是不可中断的过程),对信号量进行操作,具体定义如下:
P(S):①将信号量S的值减1,即S=S-1;
②如果S>=0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。
V(S):①将信号量S的值加1,即S=S+1;
②如果S>0,则该进程继续执行;否则释放队列中第一个等待信号量的进程。
利用信号量和PV操作实现进程互斥的一般模型是:
进程P1 进程P2 …… 进程Pn
…… …… ……
P(S); P(S); P(S);
临界区; 临界区; 临界区;
V(S); V(S); V(S);
…… …… …… ……
使用PV操作实现进程互斥时应该注意的是:
(1)每个程序中用户实现互斥的P、V操作必须成对出现,先做P操作,进临界区,后做V操作,出临界区。若有多个分支,要认真检查其成对性。
(2)P、V操作应分别紧靠临界区的头尾部,临界区的代码应尽可能短,不能有死循环。
(3)互斥信号量的初值一般为1。
利用信号量和PV操作实现进程同步:
PV操作是典型的同步机制之一。用一个信号量与一个消息联系起来,当信号量的值为0时,表示期望的消息尚未产生;当信号量的值非0时,表示期望的消息已经存在。用PV操作实现进程同步时,调用P操作测试消息是否到达,调用V操作发送消息。
使用PV操作实现进程同步时应该注意的是:
(1)分析进程间的制约关系,确定信号量种类。在保持进程间有正确的同步关系情况下,哪个进程先执行,哪些进程后执行,彼此间通过什么资源(信号量)进行协调,从而明确要设置哪些信号量。
(2)信号量的初值与相应资源的数量有关,也与P、V操作在程序代码中出现的位置有关。
(3)同一信号量的P、V操作要成对出现,但它们分别在不同的进程代码中。
2、例子:生产者/消费者模型
(1)一个生产者,一个消费者,公用一个缓冲区。
定义两个同步信号量:
S_empty——表示缓冲区空位数量,初值为1。
S_ full ——表示缓冲区占位数量,初值为0。
伪代码:
生产者进程
while(TRUE){
生产一个产品;
P(S_empty); //buff有空的吗?
产品送往Buffer;
V(S_full); //buff已满
}
消费者进程
while(True){
P(S_full); //buff有数据?
从Buffer取出一个产品;
V(S_empty); //buff有空
消费该产品;
}
(2)一个生产者,一个消费者,公用n个(单元的)环形缓冲区。
定义两个同步信号量:
S_empty——表示缓冲区空位数量,初值为n。
S_full ——表示缓冲区占位数量,初值为0。
设缓冲区的编号为1~n-1,定义两个指针in和out,分别是生产者进程和消费者进程使用的指,指向下一个可用的缓冲区。
伪代码:
生产者进程
while(TRUE){
生产一个产品;
P(S_empty);
产品送往buffer(in);
in=(in+1)mod n;
V(S_full);
}
消费者进程
while(TRUE){
P(S_full);
从buffer(out)中取出产品;
out=(out+1)mod n;
V(S_empty);
消费该产品;
}
(3)一组生产者,一组消费者,公用n个(单元的)环形缓冲区
在这个问题中,不仅生产者与消费者之间要同步,而且各个生产者之间、各个消费者之间还必须互斥地访问缓冲区。
定义四个信号量:
empty——表示缓冲区空位数量,初值为n。
full——表示缓冲区占位数量,初值为0。
mutex1——生产者之间的互斥信号量,初值为1。
mutex2——消费者之间的互斥信号量,初值为1。
设缓冲区的编号为1~n-1,定义两个指针in和out,分别是生产者进程和消费者进程使用的指针,指向下一个可用的缓冲区。
伪代码:
生产者进程
while(TRUE){
生产一个产品;
P(empty);
P(mutex1);
产品送往buffer(in);
in=(in+1)mod n;
V(mutex1);
V(full);
}
消费者进程
while(TRUE){
P(full)
P(mutex2);
从buffer(out)中取出产品;
out=(out+1)mod n;
V(mutex2);
V(empty);
消费该产品;
}
需要注意的是无论在生产者进程中还是在消费者进程中,两个P操作的次序不能颠倒。应先执行同步信号量的P操作,然后再执行互斥信号量的P操作,否则可能造成进程死锁。
原文链接:https://blog.csdn.net/lixiangsheng2012/article/details/83421359
代码示例:
当有进程要求使用共享资源时,需要执行以下操作:
1.系统首先要检测该资源的信号量;
2.若该资源的信号量值大于0,则进程可以使用该资源,此时,进程将该资源的信号量值减1;
3.若该资源的信号量值为0,则进程进入休眠状态,直到信号量值大于0时进程被唤醒,访问该资源;
当进程不再使用由一个信号量控制的共享资源时,该信号量值增加1,如果此时有进程处于休眠状态等待此信号量,则该进程会被唤醒。
2.信号量的具体结构
每个信号量集都有一个与其相对应的结构,该结构定义如下:
/* Data structure describing a set of semaphores. */
struct semid_ds
{
struct ipc_perm sem_perm; /* operation permission struct */
struct sem *sem_base; /* ptr to array of semaphores in set */
unsigned short sem_nsems; /* # of semaphores in set */
time_t sem_otime; /* last-semop() time */
time_t sem_ctime; /* last-change time */
};
/* Data structure describing each of semaphores. */
struct sem
{
unsigned short semval; /* semaphore value, always >= 0 */
pid_t sempid; /* pid for last successful semop(), SETVAL, SETALL */
unsigned short semncnt; /* # processes awaiting semval > curval */
unsigned short semzcnt; /* # processes awaiting semval == 0 */
};
信号量集的结构图如下所示:
3.代码实现信号量
使用信号量实现生产者消费者模式
#include "stdafx.h"
#include <stdio.h>
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <conio.h>
#include <ctype.h>
#include <signal.h>
#include <iostream>
#include<Windows.h>
using namespace std;
#pragma comment(lib,"pthreadVC2.lib")
#define N 5 //消费者或者生产者的数目
#define M 10 //缓冲数目
int productin = 0; //生产者放置产品的位置
int prochaseout = 0; //消费者取产品的位置
int buff[M] = {0}; //缓冲区初始化为0,开始时没有产品。
sem_t empty_sem; // 同步信号量,当满的时候阻止生产者放产品。
sem_t full_sem; //同步信号量,当没有产品的时候阻止消费者消费。
pthread_mutex_t mutex; //互斥信号量,一次只有一个线程访问缓冲区。
int product_id = 0; //生产者id
int prochase_id = 0; //消费者id
void SignalExit(int signo)
{
printf("程序退出%d\n",signo);
return;
}
void PrintProduction()
{
printf("此时的产品队列为::");
for(int i = 0; i < M; i++ )
{
printf("%d ",buff[i]);
}
printf("\n\n");
}
//生产者方法
void* Product(void* pramter)
{
int id = ++product_id;
while(1)
{
Sleep(5000); //毫秒
sem_wait(&empty_sem); //给信号量减1操作
pthread_mutex_lock(&mutex);
productin = productin % M;
printf("生产者%d在产品队列中放入第%d个产品\n\n",id,productin+1);
buff[productin] = 1;
PrintProduction();
++productin;
pthread_mutex_unlock(&mutex); //释放互斥量对象
sem_post(&full_sem); //给信号量的值加1操作
}
}
//消费者方法///
void* Prochase( void* pramter )
{
int id = ++prochase_id;
while(1)
{
Sleep(7000);
sem_wait(&full_sem);
pthread_mutex_lock(&mutex);
prochaseout = prochaseout % M;
printf("消费者%d从产品队列中取出第%d个产品\n\n",id,prochaseout+1);
buff[prochaseout] = 0;
PrintProduction();
++prochaseout;
pthread_mutex_unlock(&mutex);
sem_post(&empty_sem);
}
}
int main()
{
cout << "生产者和消费者数目都为5,产品缓冲区为10,生产者每2秒生产一个产品,消费者每5秒消费一个产品" << endl << endl;
pthread_t productid[N];
pthread_t prochaseid[N];
int ret[N];
//初始化信号量
int seminit1 = sem_init(&empty_sem,0,M);
int seminit2 = sem_init(&full_sem,0,0);
if( seminit1 != 0 && seminit2 != 0 )
{
printf("sem_init failed !!!\n");
return 0;
}
//初始化互斥信号量
int mutexinit = pthread_mutex_init(&mutex,NULL);
if( mutexinit != 0 )
{
printf("pthread_mutex_init failed !!\n");
return 0;
}
//创建n个生产者线程
for(int i = 0; i < N; i++ )
{
ret[i] = pthread_create( &productid[i], NULL,Product,(void*)(&i) );
if( ret[i] != 0 )
{
printf("生产者%d线程创建失败!\n",i);
return 0;
}
}
//创建n个消费者线程
for(int j = 0; j < N; j++ )
{
ret[j] = pthread_create(&prochaseid[j],NULL,Prochase,NULL);
if( ret[j] != 0 )
{
printf("消费者%d线程创建失败\n",j);
return 0;
}
}
///等待线程被销毁///
for( int k = 0; k < N; k++ )
{
printf("销毁线程\n");
pthread_join(productid[k],NULL);
pthread_join(prochaseid[k],NULL);
}
return 0;
}
信号量分类:进程间信号量、线程间信号量
信号量的种类
1、按用途:进程信号量、线程信号量
2、 三种
1、posix 有名信号灯
2、posix 基于内存的信号灯(无名信号灯 ) //这个是线程用的信号量 因为无名管道就是在父子线程之间的 它也一样
3、System V信号灯(IPC对象)
进程间使用的信号灯:
1、posix 有名信号灯 使用频率不高,不做介绍
2、System V信号灯 它是一个集合一个或者多个信号灯的集合 它用于进程之间的同步控制
利用PV原语进行操作 它的函数有 :
创建信号灯:
int semget(key_t key, int num_sems, int sem_flags);
操作信号灯:
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
对信号灯的控制
int semctl(int semid,int semnum,int cmd,/*union semun arg*/);
进程间信号量用法:
线程信号量(posix)
初始化sem
int sem_init(sem_t *sem,int pshared,unsigned int value);
P操作
int sem_wait(sem_t * sem); //阻塞
int sem_trywait(sem_t * sem); //非阻塞
V操作
int sem_post(sem_t * sem);
销毁sem
int sem_destroy(sem_t *sem);
原文链接:https://blog.csdn.net/bandaoyu/article/details/84307699
sem_init函数
sem_init函数是Posix信号量操作中的函数。sem_init() 初始化一个定位在 sem 的匿名信号量。value 参数指定信号量的初始值。 pshared 参数指明信号量是由进程内线程共享,还是由进程之间共享。如果 pshared 的值为 0,那么信号量将被进程内的线程共享,并且应该放置在这个进程的所有线程都可见的地址上(如全局变量,或者堆上动态分配的变量)。
如果 pshared 是非零值,那么信号量将在进程之间共享,并且应该定位共享内存区域(见 shm_open(3)、mmap(2) 和 shmget(2))。因为通过 fork(2) 创建的孩子继承其父亲的内存映射,因此它也可以见到这个信号量。所有可以访问共享内存区域的进程都可以用 sem_post(3)、sem_wait(3) 等等操作信号量。初始化一个已经初始的信号量其结果未定义。
返回值
sem_init() 成功时返回 0;错误时,返回 -1,并把 errno 设置为合适的值。
用下面一组函数(系统调用)来实现。
原文链接:https://blog.csdn.net/tomstrong_369/article/details/54312909