经典同步问题之生产者消费者问题

拥有回忆 提交于 2020-03-09 18:18:58

本文来自个人博客:https://dunkwan.cn

Classic Problem of Synchronization(经典同步问题)

在这个部分,我们要讲述大量同步问题,像一大类并发控制问题的例子。对于测试几乎每一个最新提出的同步主题,这些问题都有被用到。在我们的这类问题的解决方案中,因为同步信号量是一种解决该问题的传统方式,所以我们使用同步信号量。然而,这些解决方案的具体实现可能在二元信号量的地方使用互斥锁。

while (true){
    ...
    /* produce an item an next_produced */
    ...
    wait(empty);
    wait(mutex);
    ...
    /* add next_produced to the buffer */
    ...
    signal(mutex);
    signal(full);
}
生产者进程结构

The Bounded-Buffer Problem(有限缓冲问题)

​ 该问题被用于说明同步原语的能力。这里,我们仅提出这个话题的普遍结构而不给出具体的实现。

​ 在我们的问题中,生产者和消费者进程共享了如下数据结构:

int n;
semaphore mutex = 1;
semaphore empty = n;
semaphore full = 0;

​ 我们假设缓冲池由n个缓冲项组成,每一个能够存一个数据项。mutex信号量提供了对于缓冲区的互斥能力,并且被初始化为1。emptyfull信号量表示空缓冲和满缓冲的数量。empty被初始化为n,full被初始化为0。

​ 如上是生产者进程的代码,下面则是消费者的代码。注意两者的对称性。我们可以将代码解释为生产者为消费者生产满缓冲项,而消费者则为生产者生产空缓冲项。

while(true){
    wait(full);
    wait(mutex);
    ...
    /* remove an item from buffer to next_consumed */
    ...
    signal(mutex);
    signal(empty);
    ...
    /* consume the item in next_consumed */
    ...
}
消费者进程结构

The Producer-Consumer Problem(生产者消费者问题)

​ 在带有有限缓冲的生产者消费者问题中,我们提出了基于信号量的解决方案。由上面可知,对于生产者消费者问题,我们使用了三个信号量emptyfullmutexemptyfull分别代表缓冲区中满缓冲和空缓冲的项数,而mutex则是二元信号量(或互斥)。对于该问题而言,应当将mutex称为互斥锁,而非二元信号量。

The Buffer

buffer代表一个元素类型为buffer_item固定大小的数组。这个数组被实现为一个循环队列。以下就是其在头文件中的定义。

/* buffer.h  */
typedef int buffer_item;
#define BUFFER_SIZE 5

​ 同时该缓冲区还被两个操作函数控制,分别是insert_itemremove_item函数。它们被生产者和消费者线程所调用。以下是它们的实现框架。

#include "buffer.h"
/* buffer */
buffer_item buffer[BUFFER_SIZE];

int insert_item(buffer_item item){
    /* insert item into buffer 
    return 0 if successful, otherwise 
    return -1 indicating an error condition */
}

int remove_item(buffer_item *item){
    /* remove an object from buffer
    placing it in item 
    return 0 if successful, otherwise
    return -1 indicating an error condition */
}

insert_itemremove_item函数将会同步上面所列的生产者和消费的模型。缓冲区也需实现初始化互斥对象mutexemptyfull信号量。

main函数将负责初始化缓冲区和创建生产者消费者线程。主函数中将会通过命令行传递以下参数。

  1. 终止前的睡眠时间
  2. 生产者线程的数量
  3. 消费者线程的数量
#include "buffer.h"

int main(int argc, char *argv[]){
    /* 1. Get command line arguments argv[1], argv[2], argv[3] */
    /* 2. Initialize buffer */
    /* 3. Create producer thread(s) */
    /* 4. Create consumer thread(s) */
    /* 5. Sleep */
    /* 6. Exit */
}

The Producer and Consumer Threads

​ 生产者要么将睡眠一个随机的时间,要么将插入一个随机数到缓冲区中。随机数将由rand函数来产生,该函数将会生成一个大小在0 ~ RAND_MAX的随机数。消费者将睡眠随机时间,在它被唤醒后,将尝试去移除缓冲区中的一项。生产和消费者线程框架如下。

#include <stdlib.h> /* required for rand() */
#include "buffer.h"

void *producer(void *param){
    buffer_item item;
    
    while(true){
        /* sleep for random period of time  */
        sleep(...);
        /* generate a random number */
        item = rand();
        if(insert_item(item))
            fprintf("report error condition\n");
        else
            printf("producer produced %d\n", item);
    }
}

void *consumer(void *param){
    buffer_item item;
    
    while(true){
        /* sleep for a random period of time */
        sleep(...);
        if(remove_item(&item))
            fprintf("report error condition");
        else
            printf("consumer consumed %d\n", itme);
    }
}

Pthreads Thread Creation and Synchronization

​ 下面将利用POSIX线程库以及信号量来进行线程创建和同步操作。

以下是具体对于生产者消费者模型的实现。

/* buffer.h */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <stdbool.h>

typedef int buffer_item;
#define BUFFER_SIZE 5
buffer_item buffer[BUFFER_SIZE];
sem_t full, empty;
pthread_mutex_t mutex;
int insert_item(int);
int remove_item(int *);
void *producer(void *);
void *consumer(void *);

/* buffer.c */
#include "buffer.h"
int in = 0;
int out = 0;
int count = 0;
int insert_item(buffer_item item)
{
    if(count == BUFFER_SIZE)
       return -1;
    buffer[in] = item;
    in = (in + 1) % BUFFER_SIZE;
    count++;
    return 0;
}

int remove_item(buffer_item *item)
{
    if(count == 0)
        return -1;
    *item = buffer[out];
    out = (out + 1) % BUFFER_SIZE;
    count--;
    return 0;
}

void *producer(void *param)
{
    buffer_item item;
    while(true){
        sleep(1);
        item = random()%100 + 1;
        sem_wait(&empty);
        pthread_mutex_lock(&mutex);
        if(insert_item(item))
            printf("report error condition for insert\n");
        else
            printf("producer produced %d\n", item);
        pthread_mutex_unlock(&mutex);
        sem_post(&full);
    }
}

void *consumer(void *param)
{
    buffer_item item;

    while(true){
        sleep(1);

        sem_wait(&full);
        pthread_mutex_lock(&mutex);
        if(remove_item(&item))
            printf("report error condition for remove\n");
        else
            printf("consumer consumed %d\n", item); 
        pthread_mutex_unlock(&mutex);
        sem_post(&empty); 
    }
}

/* main.c */
#include "buffer.h"

int main(int argc, char *argv[])
{
    if(argc > 4)
    {
        perror("args error");
        exit(1);
    }
    memset(buffer, 0, sizeof(buffer));
    pthread_t prod[atoi(argv[2])], cons[atoi(argv[3])];
    sem_init(&empty, 0, 5);
    sem_init(&full, 0, 0);
    pthread_mutex_init(&mutex, NULL);
    int i;
    for(i = 0; i < atoi(argv[2]); i++)
        pthread_create(&prod[i], NULL, producer, NULL);
    for(i = 0; i < atoi(argv[3]); i++)
        pthread_create(&cons[i], NULL, consumer, NULL);

    sleep(atoi(argv[1]));
    for(i = 0; i < atoi(argv[2]); i++)
        pthread_join(prod[i], NULL);
    for(i = 0; i < atoi(argv[3]); i++)
        pthread_join(cons[i], NULL);
    sem_destroy(&empty);
    sem_destroy(&full);
    pthread_mutex_destroy(&mutex);
    exit(0);
}

/* Makefile */
EXE=producer-consumer
CC=gcc
LIBS=-pthread
SRCS= buffer.c main.c
all : $(EXE)
producer-consumer : $(SRCS)
	$(CC) -o $@ $^ $(LIBS)

clean :
	rm -rf $(EXE)

结果如下:
producer-consumer
源代码

本文大部分内容来自操作系统概念中关于进程同步的相关内容。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!