Linux基于单链表&环形队列的多线程生产者消费者模型

生产者–消费者模型简述

  对于生产者–消费者模型,相信我们都不陌生,因为生活中,我们无时无刻不在扮演生产者或消费者。但是对于Linux中的生产者–消费者模型,大家又了解了一个什么程度?
  其实,说白了就是一个生产,一个消费的关系,而且保证生产者在不生产的时候,消费者无法消费(这跟生活中一样,只不过生活中我们是确实消费不到任何东西,但在计算机中,我们就有可能消费到无效数据,、乱数据、过期数据等等);消费者的消费欲望饱和之后,生产者不生产(同上,如果市场中大量涌入某个商品,导致市场急速饱和,那么厂家户很果断的暂时放弃生产这个商品)。但是生活中的又跟计算机中的有区别,在于:一般情况下,消费者之间并无任何关系,但是计算机中,消费者是存在竞争关系的,即A消费者拿走了这个数据,那么剩余的B,C,D,E…..等等都无法使用这个数据,因为已经被消费了。生产者之间当然是竞争关系了,两个供销商为了业绩,会竞争成为一个大型超市的供货对象,很相似的是,计算机中,多个生产者都是为了为这块空间生产数据(写数据),为了空间的使用权。而生产者与消费者之间的关系为互斥与同步,互斥就是竞争关系,因为邻界资源的访问一次只能允许一个执行分支访问,所以:如果我在写(生产),你就不能读(消费);同步关系表示为:我在写完之后,就会调用函数给你信号,唤醒你来消费,解决二者之间可能出现的饥饿问题。
  

生产者–消费者间的关系

  所以生产者与消费者之间的关系可以总结如下几点:
  生产者与生产者:互斥关系;
  消费者与消费者:互斥关系;
  生产者与消费者:互斥与同步关系。

生产者与消费者模型的实现

  实现生产者与消费者模型时我们需要用到互斥锁,与条件变量。

互斥锁

   互斥锁即为保护邻界资源的一个保护“装置”,这个我之前解释过,跟二元信号量有类似的地方。当邻界资源未有执行分支使用时,锁是开着的,只要一有执行分支取访问邻界资源,那么锁就会上锁,向外表明:这块邻界资源已经有“人”使用,别“人”来之只能等待(可以使阻塞和非阻塞),当访问邻界资源的执行分支操作完毕,告诉互斥锁,我已经访问完毕,互斥锁开锁,向外界表明:此时邻界资源无“人”使用,可以申请。互斥锁还会保证高优先级的执行分支不能一直持有锁,这样理解:每当一个执行分支操作完毕,就将他的优先级降低,让他去等待队列的后面排队。
  
   相关函数:
  

       #include <pthread.h>
       pthread_t lock//定义一个锁,如果这个锁是全局的,就使用宏来初始化,如果是局部的,就使用函数pthread_mutex_init来初始化

       int pthread_mutex_lock(pthread_mutex_t *mutex);//上锁函数
       int pthread_mutex_trylock(pthread_mutex_t *mutex);//试上锁函数,即非阻塞等待
       int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁函数

条件变量

  条件变量是用来描述邻界资源内部状态的一种方法,假如我一直在等,那么我怎么知道等待什么时候,条件变量就是用来跟这个的,当邻界资源中没有可以用来供当前执行分支访问的时候,那么我就挂起等待,并且,注意!注意!我还要解开我的锁,不然我持有锁并挂起等待,那么别的执行分支申请不到锁资源,也会陷入挂起等待,形成上一篇描述的死锁问题。当邻界资源中满足我访问的条件时,你再回来唤醒我,并且此时我再次持有锁资源,以此保证我的正常操作不被别的执行分支打扰。
  相关函数:
  

       #include <pthread.h>

       int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, 
       const struct timespec *restrict abstime);
       //计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEDOUT,结束等待
       //即当前条件不满足我的访问条件,所以是当前执行分支挂起等待
       int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);//同上,没有计时,直到满足条件为止

       int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒所有的条件变量导致进入挂起等待的执行分支
       int pthread_cond_signal(pthread_cond_t *cond);//唤醒单个

基于单链表的生产者–消费者模型

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

typedef struct list{
    struct list* next;
    int data;
}Node,*PNode;

Node* head;//定义一个全局的单链表,是两个的两个线程公共资源
pthread_cond_t procon=PTHREAD_COND_INITIALIZER;//定义条件变量并初始化
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;//定义互斥锁并初始化

void initlist(PNode* head)
{
    *head=NULL;
}

void* consumer(void* val)
{
    int i=1;
    while(i)
    {
        Node* tmp=NULL;
        pthread_mutex_lock(&lock);//上锁,保证访问安全
        while(NULL == head)//用while原因是pthread_cond_wait可能调用失败
        {
            printf("list empty,consunistd.humer just waitting...\n");
            pthread_cond_wait(&procon,&lock);//如果此时链表为空,即无法访问,所以consumer挂起等待product函数插入节点后访问,
                                             //当product函数插入节点时,会唤醒consumer
        }
        tmp=head;
        head=tmp->next;
        tmp->next=NULL;
        printf("----------------pthread tid: %u\n",pthread_self());//当前线程的tid
        printf("consumer success, total: %d\n",i++);
        pthread_mutex_unlock(&lock);//解锁
        free(tmp);
        tmp=NULL;
    }
    return NULL;
}

void* product(void* val)
{
    initlist(&head);
    int i=1;
    while(i)
    {
        pthread_mutex_lock(&lock);
        Node* node=(Node*)malloc(sizeof(Node));
        node->data=i;
        node->next=head;
        head=node;
        printf("product success,total: %d\n",i++);
        pthread_mutex_unlock(&lock);
        sleep(1);
        pthread_cond_signal(&procon);//取唤醒consumer
    }
    return NULL;
}
int main()
{
    void *ret;
    pthread_t pro,con,con1;
    pthread_create(&pro,NULL,product,NULL);
    pthread_create(&con,NULL,consumer,NULL);
    pthread_create(&con1,NULL,consumer,NULL);

    pthread_join(pro,&ret);
    pthread_join(con,&ret);
    return 0;
}

程序结果截图:
这里写图片描述
这里写图片描述

注意事项:

  提醒一点,create线程后必须join线程,不然线程不会运行,别问我为什么。我去哭会/(ㄒoㄒ)/~

基于环形队列的多线程的生产者–消费者模型

相关函数:

  初始化函数,*sem表示信号量,pshared为0时表示可供同一进程中的多个线程同步,value为信号量的数量,即邻界资源的数量

       #include <semaphore.h>

       int sem_init(sem_t *sem, int pshared, unsigned int value);

  sem_wait函数相当于二元信号量的P操作,给当前信号量表示的邻界资源的数量减一

       #include <semaphore.h>

       int sem_wait(sem_t *sem);

       int sem_trywait(sem_t *sem);

       int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

  sem_post函数相当于二元信号量的V操作,给当前信号量表示的邻界资源的数量加一

       #include <semaphore.h>

       int sem_post(sem_t *sem);

  sem_destroy函数,销毁*sem表示的信号量。

       #include <semaphore.h>

       int sem_destroy(sem_t *sem);
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <semaphore.h>
#define SIZE 10

sem_t product_sem;
sem_t consumer_sem;
static int pro=SIZE;//一开始生产者可以生产
static int con=0;//一开始没有产品可供消费
static int p=0;//p,c,d变量均是为了打印定义的
static int c=0;
static int d=0;
static int table[SIZE];//用数组表示一个环形队列

void* product_fun(void* val)
{
    int i=1;
    int index=0;
    while(i)
    {
        sem_wait(&product_sem);//申请一个可生产的空间,即申请成功表示邻界资源可写,否则表示邻界资源已满不可写入。

        table[p]=i;
        pro--;
        con++;
        printf("-----------------------------------------\n");
        printf("which product:%u\n",pthread_self());//哪个生产者生产的
        printf("product#  product success,total:%d\n",i++);//一共生产了多少个
        printf("profuct#  you have %d blank source!\n",pro);//生产者可用的生产空间
        printf("product#  you have %d data source!\n",con);//消费可供消费的产品的数量
        for(index=0;index<SIZE;index++)//输出当前生产的所有产品
        {
            if(table[index]!=0)
                printf("array[%d]:%d ",index,table[index]);
        }
        printf("\n");
        printf("-----------------------------------------\n");
        printf("\n");

        sem_post(&consumer_sem);//给消费者可供消费的产品数量加一,即将邻界资源中的可使用的数据加一
        p=(p+1)%SIZE;//下标调整
        sleep(1);
    }
    return NULL;
}

void* consumer_fun(void* val)
{
    int i=1;
    int temp=0;
    while(i)
    {
        sem_wait(&consumer_sem);// 申请一个消费者可消费的产品,即此时邻界资源中有可使用的数据。

        if(c!=0)
            d=c-1;
        temp=table[c];
        table[c]=0;
        pro++;
        con--;
        printf("##########################################\n");
        printf("which consumer:%u\n",pthread_self());//哪个消费者消费
        printf("consume: %d\n",temp);//消费的数据为temp
        printf("consumer#  you have %d data source!\n",con);//可供消费的数据量
        printf("consumer#  you have %d blank source!\n",pro);//可供生产的空间
        printf("##########################################\n");
        printf("\n");

        sem_post(&product_sem);//给邻界资源可写的资源加一,表示消费一个数据,拿走数据后,空出一个位置可写
        c=(c+1)%SIZE;
    }
}

void destroy()
{
    sem_destroy(&product_sem);
    sem_destroy(&consumer_sem);
    exit(0);
}

void initsem()
{
    signal(2,destroy);//信号捕捉函数,自定义2号信号量为销毁两个信号量
    int i=0;
    for(i=0;i<SIZE;++i)
        table[i]=0;

    sem_init(&product_sem,0,SIZE);
    sem_init(&consumer_sem,0,0);
}



int main()
{
    initsem();
    pthread_t product1,product2,consumer1,consumer2;
    pthread_create(&product1,NULL,product_fun,NULL);
    pthread_create(&product2,NULL,product_fun,NULL);
    pthread_create(&consumer1,NULL,consumer_fun,NULL);
    pthread_create(&consumer2,NULL,consumer_fun,NULL);
    pthread_join(product1,NULL);
    pthread_join(product2,NULL);
    pthread_join(consumer1,NULL);
    pthread_join(consumer2,NULL);
    return 0;
}

程序结果截图:

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

如有错误,请指出!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值