【C/C++】消费-生产者模型

关于PV,信号量,同步,互斥的说明

互斥:
是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步:
是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问,更强调进程间的协作,如同生产者消费者问题。

PV原语
PV操作是典型的同步机制之一。用一个信号量与一个消 息联系起来,当信 号量的值为0时,表示期 望的消息尚未产生;当信号 量的值非0时,表示期望的消息已经存在。用P V操作实现进程同步时,调用P操作测试消息是否到达,调用V操作发送消息,(P信号量减1,V信号量加1)

利用信号量和PV原语进行同步和互斥

利用信号量和PV操作实现进程互斥的一般模型是:
进程P1 进程P2 …… 进程Pn
……  ……  ……
P(S);  P(S);  P(S);
临界区;  临界区;  临界区;
V(S);  V(S);  V(S);
……  ……  …… ……

其中信号量S用于互斥,初值为1。
使用PV操作实现进程互斥时应该注意的是:

  1. 每个程序中用户实现互斥的P、V操作必须成对出现,先做P操作,进临界区,后做V操作,出临界区。若有多个分支,要认真检查其成对性。
  2. P、V操作应分别紧靠临界区的头尾部,临界区的代码应尽可能短,不能有死循环。
  3. 互斥信号量的初值一般为1。

利用信号量和PV操作实现进程同步:
PV操作是典型的同步机制之一。用一个信号量与一个消息联系起来,当信号量的值为0时,表示期望的消息尚未产生;当信号量的值非0时,表示期望的消息已经存在。用PV操作实现进程同步时,调用P操作测试消息是否到达,调用V操作发送消息。

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

  1. 分析进程间的制约关系,确定信号量种类。在保持进程间有正确的同步关系情况下,哪个进程先执行,哪些进程后执行,彼此间通过什么资源(信号量)进行协调,从而明确要设置哪些信号量。

  2. 同一信号量的P、V操作要成对出现,但它们分别在不同的进程代码中。

参考:
http://blog.csdn.net/knight_coder/article/details/39237393

实战API

相关API说明:

pthread_cleanup_push和pthread_cleanup_pop,的使用

/* Install a cleanup handler: ROUTINE will be called with arguments ARG
   when the thread is canceled or calls pthread_exit.  ROUTINE will also
   be called with arguments ARG when the matching pthread_cleanup_pop
   is executed with non-zero EXECUTE argument.

   pthread_cleanup_push and pthread_cleanup_pop are macros and must always
   be used in matching pairs at the same nesting level of braces.  */
#  define pthread_cleanup_push(routine, arg) \
  do {                                        \
    __pthread_cleanup_class __clframe (routine, arg)


/* Remove a cleanup handler installed by the matching pthread_cleanup_push.
   If EXECUTE is non-zero, the handler function is called. */
#  define pthread_cleanup_pop(execute) \
    __clframe.__setdoit (execute);                        \
  } while (0)

这一组函数主要用于保护锁操作区间,如果在return之前,发生异常,调用pthread_cleanup_push的回调函数,这个自定义的回调函数主要用来释放资源和锁等,避免资源浪费和死锁。如果正常返回,pthread_cleanup_pop则会调用来取消pthread_cleanup_push的回调函数。
线程接收到cancel后用一种方法来保证异常退出(也就是线程没达到终点)时可以做清理工作(主要是解锁方面),pthread_cleanup_push与pthread_cleanup_pop就是这样的。

sem_post和sem_wait的使用

typedef union
{
  char __size[__SIZEOF_SEM_T];
  long int __align;
} sem_t; //同步信号量

//=========semaphore.h as below==============================
    /*
     *  int sem_init (sem_t *sem, int pshared, unsigned int value);
     * sem指定的信号量进行初始化,设置好它的共享选项,并指定一个整数类型的初始值。
     * pshared参数控制着信号量的类型。如果 pshared的值是0,就表示它是当前里程的局部信号量;否则,其它进程就能够共享这个信号量
     */

/* Initialize semaphore object SEM to VALUE.  If PSHARED then share it
   with other processes.  */
extern int sem_init (sem_t *__sem, int __pshared, unsigned int __value)
     __THROW;
//相当于信号的P操作,等待获取信号资源,信号资源减一
#define sem_wait(sem)  (int(WAIT_OBJECT_0!=WaitForSingleObject(*sem,INFINITE))


//相当于信号的V操作,信号资源加一,释放信号资源
/* Wait for SEM being posted.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int sem_wait (sem_t *__sem);

/* Post SEM.  */
extern int sem_post (sem_t *__sem) __THROWNL;

pthread_mutex_lock和pthread_mutex_unlock的使用

pthread_mutex_lock   锁住一个临界区
pthread_mutex_lock   解锁一个临界区

pthread_cond_wait和pthread_cond_signal的使用
总结起来就是,X因为一个条件成立而处于等待状态;而线程Y让某个条件成立之后,发送唤醒X线程的信号。

//用完释放锁
/* Destroy condition variable COND.  */
extern int pthread_cond_destroy (pthread_cond_t *__cond)
     __THROW __nonnull ((1));

//线程X使条件成立,调用此函数来唤醒一个被这个condition控制而处于等待状态的线程Y,
/* Wake up one thread waiting for condition variable COND.  */
extern int pthread_cond_signal (pthread_cond_t *__cond)
     __THROWNL __nonnull ((1));

//唤醒所有被这个condition控制而处于等待状态的线程,使条件满足
/* Wake up all threads waiting for condition variables COND.  */
extern int pthread_cond_broadcast (pthread_cond_t *__cond)
     __THROWNL __nonnull ((1));


//一个等待条件成立,等待挂起线程
/* Wait for condition variable COND to be signaled or broadcast.
   MUTEX is assumed to be locked before.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int pthread_cond_wait (pthread_cond_t *__restrict __cond,
                  pthread_mutex_t *__restrict __mutex)
     __nonnull ((1, 2));

操作实例:

这个案例是实现一个多对多的消费者生产模式
sem+mutex实现,使用pthread_cleanup_push实现接收非正常退出的处理

/*
 * pc_v4.cpp
 *
 *  Created on: Jul 13, 2017
 *      Author: buqing.wang
 */

/*
 *  生产者-消费者  多对多模型
 */

#include<semaphore.h>
#include <iostream>
#include <queue>
#include<cstdlib>
#include<unistd.h>
#include<pthread.h>

#define SIZE 4


using namespace std;

int size = 8;
queue<int> products;
sem_t datasem;  // 同步信号量,当没产品时阻止消费者消费,初始化为 0,因为起始状态,仓库没有产品,消费着不可操作
sem_t blanksem;//同步信号量,当满了时阻止生产者放产品;初始化为 生产者可操作的空间大小;为0表示满存不可操作

pthread_mutex_t mutex;//异步信号量 ,每次只能有一个线程访问仓库

int product_id = 0;

void interrupt_handle(void *param){
     sem_post(&datasem);
     pthread_mutex_unlock(&mutex);
     cout<<"clean handle"<<endl;

}

void *product(void *arg)
{

    while(1)
    {
        sleep(6);

        pthread_cleanup_push(interrupt_handle,NULL);
        sem_wait(&blanksem); //生产空间减小一个
        pthread_mutex_lock(&mutex);

        char* value=(char*)arg;

        product_id++;
        products.push(product_id);
        cout<<value<<"  MAKE "<<product_id<<" TOTAL :"<<products.size()<<endl;

        /*
         * sem_post函数的作用是给信号量的值加上一个“1”,
         * 它是一个“原子操作”---即同时对同一个信号量做加“1”操作的两个线程是不会冲突的;
         * 而同时对同一个文件进行读、加和写操作的两个程序就有可能会引起冲突。
         * 信号量的值永远会正确地加一个
         *
         * 当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,
         * 选择机制同样是由线程的调度策略决定的。
         */


        sem_post(&datasem);//产品增加一个
        //cout<<"proce result :"<<endl;
        pthread_mutex_unlock(&mutex);
        pthread_cleanup_pop(0);
    }

}

void *consume(void *arg)
{

    while(1)
    {
    /**
     * 函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,
     * 表明公共资源经使用后减少。函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,
     * 它直接将信号量sem的值减一。
     *
     *
     */
        sleep(1);
        sem_wait(&datasem);
        pthread_mutex_lock(&mutex);
        char *value=(char*)arg;
        if(products.size()<SIZE){
             cout<<value<<"   no product avilable!!"<<"   TOTAL:"<<products.size()<<endl;
             pthread_mutex_unlock(&mutex);

            // cout<<"proce result :"<<endl;
             sem_post(&datasem);
             //cout<<"proce result B:"<<endl;
            // sem_post(&blanksem);

             continue;
        }

//        for(int i=0;i<=2;i++)
//        {
//        cout<<value<<"  TAKE "<<products.front()<<endl;
//        products.pop();
//        }

        cout<<value<<"  TAKE "<<products.front()<<"  TOTAL :"<<products.size()<<endl;
        products.pop();
        sem_post(&blanksem);
        pthread_mutex_unlock(&mutex);


    }
}

int main(int argc, char *argv[])
{
    pthread_t a_pr;
    pthread_t b_pr;
    pthread_t i_cm;
    pthread_t j_cm;

    char * a_p ="A producer ";
    char * b_p ="B producer ";
    char * i_c ="I comsumer ";
    char * j_c ="J comsumer ";

    /*
     *  int sem_init (sem_t *sem, int pshared, unsigned int value);
     * sem指定的信号量进行初始化,设置好它的共享选项,并指定一个整数类型的初始值。
     * pshared参数控制着信号量的类型。如果 pshared的值是0,就表示它是当前里程的局部信号量;否则,其它进程就能够共享这个信号量
     */


    sem_init(&datasem, 0, 0);
    sem_init(&blanksem,0, 4);
    pthread_create(&b_pr, NULL, product, (void*)b_p);
    pthread_create(&a_pr, NULL, product, (void*)a_p);

    pthread_create(&i_cm, NULL, consume, (void*)i_c);
    pthread_create(&j_cm, NULL, consume, (void*)j_c);


    pthread_join(b_pr, NULL);
    pthread_join(a_pr, NULL);
    pthread_join(i_cm, NULL);
    pthread_join(j_cm, NULL);
    sem_destroy(&datasem);//用来释放信号量sem
    sem_destroy(&blanksem);
    pthread_mutex_destroy(&mutex);
    return 0;
}
这个是很经典的问题 实验题目: 生产者消费者(综合性实验) 实验环境: C语言编译器 实验内容: ① 由用户指定要产生的进程及其类别,存入进入就绪队列。    ② 调度程序从就绪队列中提取一个就绪进程运行。如果申请的资源被阻塞则进入相应的等待队列,调度程序调度就绪队列中的下一个进程。进程运行结束时,会检查对应的等待队列,激活队列中的进程进入就绪队列。运行结束的进程进入over链表。重复这一过程直至就绪队列为空。    ③ 程序询问是否要继续?如果要转直①开始执行,否则退出程序。 实验目的: 通过实验模拟生产者消费者之间的关系,了解并掌握他们之间的关系及其原理。由此增加对进程同步的问题的了解。 实验要求: 每个进程有一个进程控制块(PCB)表示。进程控制块可以包含如下信息:进程类型标号、进程系统号、进程状态、进程产品(字符)、进程链指针等等。 系统开辟了一个缓冲区,大小由buffersize指定。 程序中有三个链队列,一个链表。一个就绪队列(ready),两个等待队列:生产者等待队列(producer);消费者队列(consumer)。一个链表(over),用于收集已经运行结束的进程 本程序通过函数模拟信号量的操作。 参考书目: 1)徐甲同等编,计算机操作系统教程,西安电子科技大学出版社 2)Andrew S. Tanenbaum著,陈向群,马红兵译. 现代操作系统(第2版). 机械工业出版社 3)Abranham Silberschatz, Peter Baer Galvin, Greg Gagne著. 郑扣根译. 操作系统概念(第2版). 高等教育出版社 4)张尧学编著. 计算机操作系统教程(第2版)习题解答与实验指导. 清华大学出版社 实验报告要求: (1) 每位同学交一份电子版本的实验报告,上传到202.204.125.21服务器中。 (2) 文件名格式为班级、学号加上个人姓名,例如: 电子04-1-040824101**.doc   表示电子04-1班学号为040824101号的**同学的实验报告。 (3) 实验报告内容的开始处要列出实验的目的,实验环境、实验内容等的说明,报告中要附上程序代码,并对实验过程进行说明。 基本数据结构: PCB* readyhead=NULL, * readytail=NULL; // 就绪队列 PCB* consumerhead=NULL, * consumertail=NULL; // 消费者队列 PCB* producerhead=NULL, * producertail=NULL; // 生产者队列 over=(PCB*)malloc(sizeof(PCB)); // over链表 int productnum=0; //产品数量 int full=0, empty=buffersize; // semaphore char buffer[buffersize]; // 缓冲区 int bufferpoint=0; // 缓冲区指针 struct pcb { /* 定义进程控制块PCB */ int flag; // flag=1 denote producer; flag=2 denote consumer; int numlabel; char product; char state; struct pcb * processlink; …… }; processproc( )--- 给PCB分配内存。产生相应的的进程:输入1为生产者进程;输入2为消费者进程,并把这些进程放入就绪队列中。 waitempty( )--- 如果缓冲区满,该进程进入生产者等待队列;linkqueue(exe,&producertail); // 把就绪队列里的进程放入生产者队列的尾部 void signalempty() bool waitfull() void signalfull() void producerrun() void comsuerrun() void main() { processproc(); element=hasElement(readyhead); while(element){ exe=getq(readyhead,&readytail); printf("进程%d申请运行,它是一个",exe->numlabel); exe->flag==1? printf("生产者\n"):printf("消费者\n"); if(exe->flag==1) producerrun(); else comsuerrun(); element=hasElement(readyhead); } printf("就绪队列没有进程\n"); if(hasElement(consumerhead)) { printf("消费者等待队列中有进程:\n"); display(consumerhead); } else { printf("消费者等待队列中没有进程\n"); } if(hasElement(producerhead)) { printf("生产者等待队列中有进程:\n"); display(producerhead); } else { printf("生产者等待队列中没有进程\n"); } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值