操作系统(Linux)多线程--信号量实现同步

进程通信IPC(Inter Process Communication),实现进程通信的方法有很多,管道,消息队列,共享内存,信号量和socket。


  题目为:假定有一个生产者和一个消费者,生产者每次生产一件产品,并且把生产的产品存入共享缓冲区以供消费者取走使用。

消费者每次从缓冲区内取出的一件产品去消费。禁止生产者将产品放入已满的缓冲器内,禁止消费者从缓冲器内取产品。假定缓冲

区可同时存放10件产品,用PV操作实现消费者与生产者的同步问题。


这是一道很常见的同步问题,我用信号量来实现。


信号量是用于多个进程(线程)对共享数据的访问的计数器:
  1.当信号量的值为正时,则进程(线程)可以使用该资源。在这种情况下,进程(线程)会将信号量减1,表示它使用了一个资源单位。

  2.当信号量为0,则进程进入休眠状态,直到信号量大于0。进程被唤醒后,它返回步骤1。

常用信号量形式被称作二元信号量。它控制单个资源,其初始值为1。但是信号量可以是任意一个正值,该值表示有多个共享着资源单位可共享应用。

unix提供了两种信号量,XSI和POSIX信号量。

在 本例子中用POSIX信号量实现。



信号量分为命名信号量和未命名信号量。

在使用POSIX调用sem_init函数来创建一个未命名的信号量和sem_destory丢弃使用完的信号量。


sem_init函数:

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
pshared表明是否在多个进程中使用信号量。如果不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享。
value表示信号量的初始值。

sem_t:信号量的数据结构,本质是有个长整型的数。


sem_destroy函数:

#include <semaphore.h>
int sem_destroy(sem_t *sem);

对于未命名信号的使用已经完成时,可以调用sem_destroy函数丢弃它。

调用sem_destroy后,不能再使用任何带有sem的信号函数,除非通过调用sem_init重新初始化它。



sem_wait函数:

#include <semaphore.h>
int sem_wait(sem_t *sem)

使用sem_wait函数时,实现信号量减1的操作,如果信号量计数是0就会发生阻塞。直到成功使信号量减1或者被信号中断才返回。


sem_post函数:

#include <semphore.h>
int  sem_post(sem_t *sem);


实现信号量增1的操作,在调用sem_post时,如果在调用sem_wait中发生进程阻塞。那么进程会被唤醒并且被sem_post增加1的信号量计数会再次被sem_wait减1。



首先创建一个信号量sem_mutex表示对缓冲区的操作,用sem_init(&sem_mutex,0,1)初始化。0表示使用得是线程,1表示只有一个共享资源。

每次对缓冲区进行操作时,就需要调用sem_wait(&sem_mutex)相当于P操作,表示正在操作缓冲区,由于初始值为1,所以其他线程想对其操作时,必须等待此操作调用sem_post相对于V操作,表示对其操作结束,另一个线程可以进入对其操作。这样就可以实现数据的同步,避免出现与"时间相关的错误"。


由于生产者和消费者分别用一个线程执行操作,由于线程时可以共享数据的。

定义一个结构体表示缓冲队列的资源,队列里面可以放10个字符,num表示已经放入缓冲区的数量。

当生产者执行时num++,消费者执行时num--。

rear,front便于存入和打印出字符


信号量s1和s2分别表示对缓冲区空位置的操作和表示缓冲区已经放入的产品的操作

s1和s2的初始值分别为10和1

当s1小于10时表示生产者继续向缓冲区的空位置放入产品的操作

当s2大于0时表示消费者可以从缓冲区里面继续取产品操作


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

#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>

sem_t sem_mutex;//表示对缓冲区的操作
sem_t s1;//信号量s1表示缓冲区区还可以放入的产品
sem_t s2;//信号量s2表示缓冲区已经放的产品

#define BUFF_SIZE 10

struct P_Queue{
char products[BUFF_SIZE];
int num;
int  front,rear;
};

void* Producer(void *arg)
{
    struct P_Queue *q;
    q = (struct P_Queue *)arg;
     while(1)
{
      sem_wait(&sem_mutex);
      if(q->num<BUFF_SIZE)
       {
         sem_wait(&s1);
         char c1;//输入的字符
         char c2;//回车键
         printf("input\n");
         scanf("%c",&c1);
         c2 = getchar();
         q->rear = (q->rear+1)%BUFF_SIZE;
         q->products[q->rear]=c1;
         printf("Products: %c\n",c1);
         q->num++;
         printf("Buffer Size:%d\n",q->num);
         sem_post(&s2);
 }
       sem_post(&sem_mutex);
       sleep(rand()%2);
}
     return NULL;
}

void* Consumer(void *arg)
{
   struct P_Queue *q;
   q = (struct P_Queue*)arg;
   while(1)
{
      sem_wait(&sem_mutex);
      if(q->num>0)
      {
       sem_wait(&s2);
       q->front = (q->front+1)%BUFF_SIZE;
       char c = q->products[q->front];
       q->num--;
       printf("Consumer show content: %c\n",c);
       printf("Buffer Size: %d\n",q->num);
       sem_post(&s1);
      }
      sem_post(&sem_mutex);
      sleep(rand()%2);
}
}

int main()
{
   struct P_Queue *q;
   q = (struct P_Queue *)malloc(sizeof(struct P_Queue));
   q->front = q->rear = 0;
   q->num =0;
   pthread_t pid1,cid1;
   sem_init(&s1,0,BUFF_SIZE);
   sem_init(&s2,0,0);
   sem_init(&sem_mutex,0,1);
   pthread_create(&pid1,NULL,Producer,(void*)q);
   pthread_create(&cid1,NULL,Consumer,(void*)q);
   pthread_join(pid1,NULL);
   pthread_join(cid1,NULL);
   printf("hello");
   sem_destory(&s1);
   sem_destory(&s2);
   sem_destory(&sem_mutex);
   return 0;
}

效果:


由于线程调度是由操作系统控制的,所以并不是执行一次生产者,就接着执行一次消费者。

注意运行的时候应该加 -lpthread,不然会找不到sem_wait,sem_post,sem_init等函数


信号量分为命名信号量和未命名信号量。

在使用POSIX调用sem_init函数来创建一个未命名的信号量和sem_destory丢弃使用完的信号量。

首先创建一个信号量sem_mutex表示对缓冲区的操作,用sem_init(&sem_mutex,0,1)初始化。0表示使用得是线程,1表示只有一个共享资源。

猜你喜欢

转载自blog.csdn.net/qq_23948283/article/details/52895141
今日推荐