【linux】信号量与PV操作 (进程和线程的同步)

目录

1、基本含义

使用PV操作实现进程互斥时应该注意的是:

2、例子:生产者/消费者模型

3.代码实现信号量

信号量分类:进程间信号量、线程间信号量


在计算机操作系统中,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

  • 2
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux中,可以使用信号量和PV原语操作机制实现进程同步和互斥。 1. 同步机制:可以使用信号量来实现进程同步信号量是一种计数器,它用来控制多个进程对共享资源的访问。在Linux中,信号量由semget、semop和semctl三个系统调用来完成。 2. 互斥机制:可以使用PV原语操作机制来实现进程的互斥。PV原语操作机制可以实现原子操作,保证多个进程对共享资源的访问是互斥的。在Linux中,PV原语操作机制由semaphore.h头文件中的sem_init、sem_wait和sem_post三个函数来实现。 下面是一个简单的示例代码: ``` #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <semaphore.h> sem_t sem; // 定义信号量 void *thread_func(void *arg) { sem_wait(&sem); // 等待信号量 printf("Thread %d is running\n", *(int *)arg); sem_post(&sem); // 发送信号量 return NULL; } int main() { sem_init(&sem, 0, 1); // 初始化信号量 pthread_t tid[5]; int i; for (i = 0; i < 5; i++) { int *arg = malloc(sizeof(int)); *arg = i; pthread_create(&tid[i], NULL, thread_func, arg); // 创建线程 } for (i = 0; i < 5; i++) { pthread_join(tid[i], NULL); // 等待线程结束 } sem_destroy(&sem); // 销毁信号量 return 0; } ``` 在上面的示例代码中,我们使用了sem_wait和sem_post函数来实现线程同步。在每个线程中,我们使用sem_wait函数等待信号量,当信号量的值为1时,线程可以继续执行。在线程执行完后,我们使用sem_post函数发送信号量,将信号量的值加1,以便其他线程可以继续执行。同时,我们使用sem_init函数初始化信号量,使用sem_destroy函数销毁信号量
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值