生产者-消费者模型Python(操作系统实验)

实验五: 生产者-消费者模型

实验内容

模拟操作系统中进程同步和互斥。

实验目的

• 熟悉临界资源、信号量及PV操作的定义与物理意义

• 了解进程通信的方法

• 掌握进程互斥与进程同步的相关知识

• 掌握用信号量机制解决进程之间的同步与互斥问题

• 实现生产者-消费者问题,深刻理解进程同步问题

实验题目

在Linux操作系统下用C实现经典同步问题:生产者— 消费者,具体要求如下:

  1. 一个大小为10的缓冲区,初始状态为空。

  2. 2个生产者,随机等待一段时间,往缓冲区中添加数据,若缓冲区已满,等待消费者取走数据之后再添加,重复10次。

  3. 2个消费者,随机等待一段时间,从缓冲区中读取数据,若缓冲区为空,等待生产者添加数据之后再读取,重复10次。

  • 提示

    本实验的主要目的是模拟操作系统中进程同步和互斥。在 系统进程并发执行异步推进的过程中,由于资源共享和进程间 合作而造成进程间相互制约。进程间的相互制约有两种不同的方式。

    1. 间接制约。这是由于多个进程共享同一资源(如CPU 、共享输入/输出设备)而引起的,即共享资源的多个进程因系统协调使用资源而相互制约。
    2. 直接制约。只是由于进程合作中各个进程为完成同一 任务而造成的,即并发进程各自的执行结果互为对方的执行条 件,从而限制各个进程的执行速度。

    生产者和消费者是经典的进程同步问题,在这个问题中,生产者不断的向缓冲区中写入数据,而消费者则从缓冲区中读取数据。生产者进程和消费者对缓冲区的操作是互斥,即当前只能有一个进程对这个缓冲区进行操作,生产者进入操作缓冲区之前,先要看缓冲区是否已满,如果缓冲区已满,则它必须等待消费者进程将数据取出才能写入数据,同样的,消费者进程从缓冲区读取数据前,也要判断缓冲区是否为空,如果为空,则必须等待生产者进程写入数据才能读取数据。

    在本实验中,进程之间要进行通信来操作同一缓冲区。一般来说,进程间的通信根据通信内容可以划分为两种:即控制信息的传送与大批量数据传送。有时,也把进程间控制信息的交换称为低级通信,而把进程间大批量数据的交换称为高级通信。
    目前,计算机系统中用得比较普遍的高级通信机制可分为 3 大类:共享存储器系统、消息传递系统及管道通信系统。

  • 共享存储器系统

    共享存储器系统为了传送大量数据,在存储器中划出一块共享存储区,诸进程可通过对共享存储区进行读数据或写数据以实现通信。进程在通信之前,向系统申请共享存储区中的一个分区,并为它指定一个分区关键字。

  • 消息传递系统
    在消息传递系统中,进程间的数据交换以消息为单位,在计算机网络中被称为报文。消息传递系统的实现方式又可以分为以下两种:

    1. 直接通信方式:发送进程可将消息直接发送给接收进程,即将消息挂在接收进程的消息缓冲队列上,而接收进程可从自己的消息缓冲队列中取得消息。
    2. 间接通信方式:发送进程将消息发送到指定的信箱中,而接收进程从信箱中取得消息。这种通信方式又称信箱通信方式,被广泛地应用于计算机网络中。相应地,该消息传递系统被称为电子邮件系统。
  • 管道通信系统

    向管道提供输入的发送进程,以字符流方式将大量的数据送入管道,而接收进程从管道中接收数据。由于发送进程和接收进程是利用管道进行通信的,故称为管道通信。

    为了协调发送和接收双方的通信,管道通信机制必须提供以下3方面的协调功能。

    1. 互斥:当一个进程正在对 pipe 文件进行读或写操作时,另一个进程必须等待。
    2. 同步:当写进程把一定数量的数据写入 pipe 文件后,便阻塞等待,直到读进程取走数据后,再把写进程唤醒。
    3. 确认对方是否存在:只有确定对方已存在时,才能进行管道通信,否则会造成因对方不存在而无限制地等待。

    在这个问题当中,我们采用信号量机制进行进程之间的通信,设置两个信号量,空的信号量和满的信号量。
    在 Linux 系统中,一个或多个信号量构成一个信号量集合。使用信号量机制可以实现进程之间的同步和互斥,允许并发进程一次对一组信号量进行相同或不同的操作。每个 P、V 操作不限于减 1 或加 1,而是可以加减任何整数。在进程终止时,系统可根据需要自动消除所有被进程操作过的信号量的影响

    ① 缓冲区采用循环队列表示,利用头、尾指针来存放、读取数据,以及判断队列是否为空。缓冲区中数组大小为 10;
    ② 利用随机函数 rand()得到 A~Z 的一个随机字符,作为生产者每次生产的数据,存放到缓冲区中;
    ③ 使用 shmget()系统调用实现共享主存段的创建, shmget()返回共享内存区的 ID。对于已经申请到的共享段,进程需把它附加到自己的虚拟空间中才能对其进行读写。
    ④ 信号量的建立采用 semget()函数,同时建立信号量的数量。在信号量建立后,调用 semctl()对信号量进行初始化,例如本实验中,可以建立两个信号量 SEM_EMPTY、 SEM_FULL,初始化时设置 SEM_EMPTY 为 10, SEM_FULL 为 0。使用操作信号的函数 semop()做排除式操作,使用这个函数防止对共享内存的同时操作。对共享内存操作完毕后采用 shmctl() 函数撤销共享内存段。
    ⑤ 使用循环,创建 2 个生产者以及 2 个消费者,采用函数 fork()创建一个新的进程。
    ⑥ 一个进程的一次操作完成后,采用函数 fflush()刷新缓冲区。
    ⑦ 程序最后使用 semctl()函数释放内存。

    模拟程序的程序流程图如下所示:

    1. 主程序流程图

      在这里插入图片描述

    2. 生产者进程流程图

      在这里插入图片描述

    3. 消费者流程图

      在这里插入图片描述

    4. P操作流程图

      在这里插入图片描述

    5. V操作流程图

      在这里插入图片描述

实验总结

  • 引入需要使用的库
"""生产者-消费者模型"""
import time
import random
from multiprocessing import Process, Semaphore, Manager
import multiprocessing
import argparse
import os
import pandas as pd

其中multiprocessingPython提供的一个进程库,能够创建新进程,argparse是命令行解析工具,作用是方便程序在命令行的调用,os是调用终端执行命令的工具包,能够做一些想清屏等操作,pandas是数据处理工具,可以将数据到处xlsx表格.

  • 使用命令行解析工具构建需要的参数
    # 创建一个命令行解析器对象
    parser = argparse.ArgumentParser(description='Producer-Consumer-Model')
    # 添加参数
    parser.add_argument('buffer_size', type=int, default=10,
                        help='The size of buffer area.')
    parser.add_argument('--exe', action='store_true',
                        help="Execute the Producer-Consumer-Model with the user's <input argument> or default <10>")
    # 参数解析到agrs中
    args = parser.parse_args()
  • 接着在主程序中定义一些变量,比如缓冲区大小,索引下标,互斥/同步信号量
        # 缓冲区大小
        buffer_size = args.buffer_size

        # 缓冲区下一个存放/取出的索引
        fin = fot = 0
        buffer = [chr(48) for _ in range(buffer_size)]

        # 互斥信号量
        mutex = Semaphore(1)

        # 同步信号量
        empty = Semaphore(buffer_size)
        full = Semaphore(0)
  • 然后将一些数据通过multiprocess.Manager()进行共享
        # 将有关buffer缓冲区的内容通过Manager的方法进行共享
        mydict = Manager().dict()
        mylist = Manager().list(buffer)
        output = Manager().list()
        mydict['buffer_size'] = buffer_size
        mydict['fin'] = fin
        mydict['fot'] = fot
  • 创建消费者和生产者进程对象
        # 创建消费者进程
        consumer1 = Process(target=consumer, args=(mydict, mylist, mutex, empty, full, output))
        consumer2 = Process(target=consumer, args=(mydict, mylist, mutex, empty, full, output))

        # 创建生产者进程
        producer1 = Process(target=producer, args=(mydict, mylist, mutex, empty, full, output))
        producer2 = Process(target=producer, args=(mydict, mylist, mutex, empty, full, output))
  • 进程开始执行,调用start()运行
        # 进程开始运行,相当于fork()
        producer1.start()
        producer2.start()
        consumer1.start()
        consumer2.start()
  • 等待进程结束,通过join()实现
        # 等待进程结束
        producer1.join()
        producer2.join()
        consumer1.join()
        consumer2.join()
  • 程序的最后是将数据进行输出到xlsx表格当中
        df = pd.DataFrame(list(output), columns=['Executing Time', 'Buffer Data', 'Process Name', 'Consume/Produce'])
        df.to_excel('./output.xlsx')
  • 以下是生产者模型的代码
def producer(mydict: dict, mylist: list, Mutex, Empty, Full, Out):
    # 生产者的生产操作的次数
    cnt = 0
    # 每个生产者生产十次产品
    while cnt < 10:
        # 首先进行同步信号的判断,看有没有空位供存放
        if Empty.get_value() <= 0:
            Nothing = None  # Do nothing
            # print(f"{time.strftime('%H:%M:%S')} seconds | Buffer full! Producer: {multiprocessing.current_process().name} is waiting...")
        Empty.acquire()
        if Mutex.get_value() <= 0:
            Nothing = None  # Do nothing
            # print(f"{time.strftime('%H:%M:%S')} seconds | A process is executing! Producer: {multiprocessing.current_process().name} is waiting...")
        Mutex.acquire()
        # 随机一个生产时间,一般长于消费时间
        time.sleep(random.randint(2, 4))
        # 随机产生一个A~Z的字符
        element = chr(random.randint(0, 25) + 65)
        # print(f"{time.strftime('%H:%M:%S')} seconds | Producer: {multiprocessing.current_process().name} has produced a product ({element}) that put in the {mydict['fin']}th position of the buffer.")

        # 更新mylist, cnt, fin等等
        mylist[mydict['fin']] = element

        # print('Executing Time\t\tBuffer Data\t\t\t\t\t\t\tProcess Name\t\tConsume/Produce')
        print(f"{time.strftime('%H:%M:%S')}\t\t{mylist}\t\t{multiprocessing.current_process().name}-P\t\t{element}")
        Out.append([time.strftime('%H:%M:%S'), list(mylist), multiprocessing.current_process().name + '-P', element])

        cnt += 1
        mydict['fin'] = (mydict['fin'] + 1) % mydict['buffer_size']
        # 最后做V操作,没有顺序
        Mutex.release()
        Full.release()
        
        # 在下次操作前,随机等待一段时间
        time.sleep(random.randint(1, 4))

外循环是while cnt < 10:是因为每个进程需要进行十次操作,然后是对同步信号和互斥信号的P操作,需要注意的是首先需要对同步信号量进行P操作然后再是互斥信号量,在执行完P操作之后,就是进行生产这行为,包括一系列的更新工作等,最后在执行完生产操作之后就相对应的执行V操作.

  • 消费者模型的代码
def consumer(mydict: dict, mylist: list, Mutex, Empty, Full, Out):
    # cnt统计消费者的消费次数
    cnt = 0
    # 每个消费者,进行十次的消费行为
    while cnt < 10:
        # 这个变量的意义是为了防止while打印非常多次的提示语句,一次就行了
        if Full.get_value() <= 0:
            Nothing = None  # Do nothing
            # 如果是因为同步信号量,输出:{} seconds | Buffer empty! Consumer: {} is waiting...
            # print(f"{time.strftime('%H:%M:%S')} seconds | Buffer empty! Consumer: {multiprocessing.current_process().name} is waiting...")
        Full.acquire()
        if Mutex.get_value() <= 0:
            Nothing = None  # Do nothing
            # 如果是因为互斥信号量,输出:{} seconds | A process is executing! Consumer: {} is waiting...
            # print(f"{time.strftime('%H:%M:%S')} seconds | A process is executing! Consumer: {multiprocessing.current_process().name} is waiting...")
        Mutex.acquire()
        # 随机一个消费操作的耗时
        time.sleep(random.randint(1, 2))
        # print(f"{time.strftime('%H:%M:%S')} seconds | Consumer: {multiprocessing.current_process().name} has consumed a product ({mylist[mydict['fot']]}) that located in the {mydict['fot']}th position of the buffer.")

        # print('Executing Time\t\tBuffer Data\t\t\t\t\t\t\tProcess Name\t\tConsume/Produce')
        print(
            f"{time.strftime('%H:%M:%S')}\t\t{mylist}\t\t{multiprocessing.current_process().name}-C\t\t{mylist[mydict['fot']]}")
        Out.append([time.strftime('%H:%M:%S'), list(mylist), multiprocessing.current_process().name + '-C', mylist[mydict['fot']]])
        # 对消费后的内容重置,同时cnt自增一表示消费者执行一次消费操作,另外更新fot即out的位置
        mylist[mydict['fot']] = chr(48)

        cnt += 1
        mydict['fot'] = (mydict['fot'] + 1) % mydict['buffer_size']
        # 最后一步做V操作,没有顺序
        Mutex.release()
        Empty.release()
      
        # 在下次操作前,随机等待一段时间
        time.sleep(random.randint(1, 4))

外循环是while cnt < 10:也是因为每个进程需要进行十次操作,然后是对同步信号和互斥信号的P操作,、在执行完P操作之后,就是进行消费的行为,包括一系列的更新工作等,最后在执行完生产操作之后就相对应的执行V操作,流程和生产者一致.

下面是整体的程序代码:

"""生产者-消费者模型"""
import time
import random
from multiprocessing import Process, Semaphore, Manager
import multiprocessing
import argparse
import os
import pandas as pd

"""消费者模型"""


def consumer(mydict: dict, mylist: list, Mutex, Empty, Full, Out):
    # cnt统计消费者的消费次数
    cnt = 0
    # 每个消费者,进行十次的消费行为
    while cnt < 10:
        # 这个变量的意义是为了防止while打印非常多次的提示语句,一次就行了
        if Full.get_value() <= 0:
            Nothing = None  # Do nothing
            # 如果是因为同步信号量,输出:{} seconds | Buffer empty! Consumer: {} is waiting...
            # print(f"{time.strftime('%H:%M:%S')} seconds | Buffer empty! Consumer: {multiprocessing.current_process().name} is waiting...")
        Full.acquire()
        if Mutex.get_value() <= 0:
            Nothing = None  # Do nothing
            # 如果是因为互斥信号量,输出:{} seconds | A process is executing! Consumer: {} is waiting...
            # print(f"{time.strftime('%H:%M:%S')} seconds | A process is executing! Consumer: {multiprocessing.current_process().name} is waiting...")
        Mutex.acquire()
        # 随机一个消费操作的耗时
        time.sleep(random.randint(1, 2))
        # print(f"{time.strftime('%H:%M:%S')} seconds | Consumer: {multiprocessing.current_process().name} has consumed a product ({mylist[mydict['fot']]}) that located in the {mydict['fot']}th position of the buffer.")

        # print('Executing Time\t\tBuffer Data\t\t\t\t\t\t\tProcess Name\t\tConsume/Produce')
        print(
            f"{time.strftime('%H:%M:%S')}\t\t{mylist}\t\t{multiprocessing.current_process().name}-C\t\t{mylist[mydict['fot']]}")
        Out.append([time.strftime('%H:%M:%S'), list(mylist), multiprocessing.current_process().name + '-C', mylist[mydict['fot']]])
        # 对消费后的内容重置,同时cnt自增一表示消费者执行一次消费操作,另外更新fot即out的位置
        mylist[mydict['fot']] = chr(48)

        cnt += 1
        mydict['fot'] = (mydict['fot'] + 1) % mydict['buffer_size']
        # 最后一步做V操作,没有顺序
        Mutex.release()
        Empty.release()
        
        # 在下次操作前,随机等待一段时间
        time.sleep(random.randint(1, 4))


"""生产者模型"""


def producer(mydict: dict, mylist: list, Mutex, Empty, Full, Out):
    # 生产者的生产操作的次数
    cnt = 0
    # 每个生产者生产十次产品
    while cnt < 10:
        # 首先进行同步信号的判断,看有没有空位供存放
        if Empty.get_value() <= 0:
            Nothing = None  # Do nothing
            # print(f"{time.strftime('%H:%M:%S')} seconds | Buffer full! Producer: {multiprocessing.current_process().name} is waiting...")
        Empty.acquire()
        if Mutex.get_value() <= 0:
            Nothing = None  # Do nothing
            # print(f"{time.strftime('%H:%M:%S')} seconds | A process is executing! Producer: {multiprocessing.current_process().name} is waiting...")
        Mutex.acquire()
        # 随机一个生产时间,一般长于消费时间
        time.sleep(random.randint(2, 4))
        # 随机产生一个A~Z的字符
        element = chr(random.randint(0, 25) + 65)
        # print(f"{time.strftime('%H:%M:%S')} seconds | Producer: {multiprocessing.current_process().name} has produced a product ({element}) that put in the {mydict['fin']}th position of the buffer.")

        # 更新mylist, cnt, fin等等
        mylist[mydict['fin']] = element

        # print('Executing Time\t\tBuffer Data\t\t\t\t\t\t\tProcess Name\t\tConsume/Produce')
        print(f"{time.strftime('%H:%M:%S')}\t\t{mylist}\t\t{multiprocessing.current_process().name}-P\t\t{element}")
        Out.append([time.strftime('%H:%M:%S'), list(mylist), multiprocessing.current_process().name + '-P', element])

        cnt += 1
        mydict['fin'] = (mydict['fin'] + 1) % mydict['buffer_size']
        # 最后做V操作,没有顺序
        Mutex.release()
        Full.release()
        
        # 在下次操作前,随机等待一段时间
        time.sleep(random.randint(1, 4))


if __name__ == '__main__':
    # 创建一个命令行解析器对象
    parser = argparse.ArgumentParser(description='Producer-Consumer-Model')
    # 添加参数
    parser.add_argument('buffer_size', type=int, default=10,
                        help='The size of buffer area.')
    parser.add_argument('--exe', action='store_true',
                        help="Execute the Producer-Consumer-Model with the user's <input argument> or default <10>")
    # 参数解析到agrs中
    args = parser.parse_args()
    if args.exe:
        os.system('cls')
        print(f'\n{args}\n')
        # 打印以下详细信息,这个consumer_producerModel.py是该程序的文件名,可自行修改
        os.system('python consumer_producerModel.py -h')
        print()
        # 缓冲区大小
        buffer_size = args.buffer_size

        # 缓冲区下一个存放/取出的索引
        fin = fot = 0
        buffer = [chr(48) for _ in range(buffer_size)]

        # 互斥信号量
        mutex = Semaphore(1)

        # 同步信号量
        empty = Semaphore(buffer_size)
        full = Semaphore(0)

        # 将有关buffer缓冲区的内容通过Manager的方法进行共享
        mydict = Manager().dict()
        mylist = Manager().list(buffer)
        output = Manager().list()
        mydict['buffer_size'] = buffer_size
        mydict['fin'] = fin
        mydict['fot'] = fot

        # 创建消费者进程
        consumer1 = Process(target=consumer, args=(mydict, mylist, mutex, empty, full, output))
        consumer2 = Process(target=consumer, args=(mydict, mylist, mutex, empty, full, output))

        # 创建生产者进程
        producer1 = Process(target=producer, args=(mydict, mylist, mutex, empty, full, output))
        producer2 = Process(target=producer, args=(mydict, mylist, mutex, empty, full, output))

        print('Executing Time\t\tBuffer Data\t\t\t\t\t\t\tProcess Name\t\tConsume/Produce')
        # 进程开始运行,相当于fork()
        producer1.start()
        producer2.start()
        consumer1.start()
        consumer2.start()

        # 等待进程结束
        producer1.join()
        producer2.join()
        consumer1.join()
        consumer2.join()

        df = pd.DataFrame(list(output), columns=['Executing Time', 'Buffer Data', 'Process Name', 'Consume/Produce'])
        df.to_excel('./output.xlsx')

以下是运行结果截图(含表格的截图):

在这里插入图片描述

在这里插入图片描述

对于思考题,无非就是将进程库换成线程库,但是要注意的是线程可进程的区别,对于同一个进程的线程,它们是会有独立的寄存器和栈空间的,但是会共享其他的大部分数据空间. 由于原理是一样的,我们可以使用一种更加简单的方式实现,即队列,另外使用的是线程,具体代码如下:

import threading
import queue
import time
import random

buffer = queue.Queue()


def Producer(name):
    cnt = 0
    while cnt < 10:
        while buffer.qsize() < 10:
            element = chr(random.randint(0, 25) + 65)
            buffer.put(element)
            print(f"{name} has put '{element}' in the buffer\n")
            cnt += 1
            time.sleep(1)


def Consumer(name):
    cnt = 0
    while cnt < 10:
        while buffer.qsize():
            print(f'{name} get {buffer.get()} from buffer\n')
            time.sleep(1)
            cnt += 1


t1 = threading.Thread(target=Producer, args=("p1",))
t2 = threading.Thread(target=Producer, args=("p2",))
t3 = threading.Thread(target=Consumer, args=("c1",))
t4 = threading.Thread(target=Consumer, args=("c2",))

t1.start()
t2.start()
t3.start()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

call me Patrick

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值