【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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的 C 生产者消费模型: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define BUFFER_SIZE 10 int buffer[BUFFER_SIZE]; // 缓冲区数组 int count = 0; // 缓冲区计数器 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 互斥锁 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 条件变量 void *producer(void *arg) { int i; for (i = 0; i < BUFFER_SIZE; i++) { pthread_mutex_lock(&mutex); while (count == BUFFER_SIZE) { // 缓冲区满了 pthread_cond_wait(&cond, &mutex); // 等待消费消费 } buffer[count++] = i; // 生产物品并放入缓冲区 printf("producer produced %d\n", i); pthread_cond_signal(&cond); // 通知消费者可以消费了 pthread_mutex_unlock(&mutex); } pthread_exit(NULL); } void *consumer(void *arg) { int i; for (i = 0; i < BUFFER_SIZE; i++) { pthread_mutex_lock(&mutex); while (count == 0) { // 缓冲区空了 pthread_cond_wait(&cond, &mutex); // 等待生产者生产 } int item = buffer[--count]; // 从缓冲区取出物品并消费 printf("consumer consumed %d\n", item); pthread_cond_signal(&cond); // 通知生产者可以生产了 pthread_mutex_unlock(&mutex); } pthread_exit(NULL); } int main() { pthread_t producer_thread, consumer_thread; // 创建生产者消费者线程 pthread_create(&producer_thread, NULL, producer, NULL); pthread_create(&consumer_thread, NULL, consumer, NULL); // 等待线程结束 pthread_join(producer_thread, NULL); pthread_join(consumer_thread, NULL); return 0; } ``` 在上面的代码中,生产者线程和消费者线程共享一个缓冲区数组和一个计数器。生产者向缓冲区中添加物品,并将计数器加一;消费者从缓冲区中取出物品,并将计数器减一。如果缓冲区已满,生产者就会等待消费消费;如果缓冲区已空,消费者就会等待生产者生产。在等待过程中,线程会释放互斥锁,以允许其他线程访问缓冲区。当条件变量发生变化时,线程会重新获取互斥锁并继续执行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值