操作系统:进程通信实践-同步又有互斥的信号量机制(详解)

目录

请设计进程既有同步又有互斥的应用场景,并尝试用信号量机制实现。可尝试用有名或无名信号量代码实现上述过程,并给出代码截图、调试过程和运行结果截图。当交换互斥和同步的P,V操作顺序时,程序运行结果是什么?

使用无名信号量的代码

信号量的作用(mutex、full和empty)

生产者线程(producer)

消费者线程(consumer)

缓冲区位置索引(in和out)

 调试过程

 结果截图

使用有名信号量的代码

 交换互斥和同步的P,V操作顺序时,程序运行结果

应用场景-火车票售票过程

运行结果截图


请设计进程既有同步又有互斥的应用场景,并尝试用信号量机制实现。可尝试用有名或无名信号量代码实现上述过程,并给出代码截图、调试过程和运行结果截图。当交换互斥和同步的P,V操作顺序时,程序运行结果是什么?

使用无名信号量的代码

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

#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int in = 0;
int out = 0;
sem_t mutex;
sem_t full;
sem_t empty;

// producer
void *producer(void *arg) {
    int item;
    while (1) {
        item = rand() % 100;
        sem_wait(&empty);
        sem_wait(&mutex);
        buffer[in]=item;
        printf("Producer produced item %d at position %d\n", item, in);
        in = (in + 1) % BUFFER_SIZE;
        sem_post(&mutex);
        sem_post(&full);
        sleep(1);
    }
    return NULL;
}

// consumer
void *consumer(void *arg) {
    int item;
    while (1) {
        sem_wait(&full);
        sem_wait(&mutex);
        item = buffer[out];
        printf("Consumer consumed item %d from position %d\n", item, out);
        out = (out + 1) % BUFFER_SIZE;
        sem_post(&mutex);
        sem_post(&empty);
        sleep(2);
    }
    return NULL;
}

int main() {
    pthread_t producer_thread, consumer_thread;
    sem_init(&mutex, 0, 1);
    sem_init(&full, 0, 0);
    sem_init(&empty, 0, BUFFER_SIZE);
    pthread_create(&producer_thread, NULL, producer, NULL);
    pthread_create(&consumer_thread, NULL, consumer, NULL);
    pthread_join(producer_thread, NULL);
    pthread_join(consumer_thread, NULL);
    sem_destroy(&mutex);
    sem_destroy(&full);
    sem_destroy(&empty);
    return 0;
}


  • 信号量的作用(mutexfullempty)

    • 定义了三个信号量:mutexfullempty

      • mutex信号量用于实现对共享缓冲区的互斥访问。其初始值为1,表示初始时缓冲区是可被访问的。在生产者和消费者访问缓冲区时,都需要先获取mutex信号量,以确保同一时刻只有一个线程(生产者或消费者)在操作缓冲区。

      • full信号量用于表示缓冲区中已有的数据项数量。初始值为0,因为开始时缓冲区是空的。当生产者向缓冲区放入一个数据项后,会对full信号量执行sem_post操作(增加信号量的值);消费者在从缓冲区取数据前,会先执行sem_wait操作(如果full信号量的值为0则阻塞,直到有数据可供消费)。

      • empty信号量用于表示缓冲区中空闲位置的数量。初始值为BUFFER_SIZE(这里定义为5),表示缓冲区初始时有5个空闲位置。生产者在向缓冲区放入数据前,会先执行sem_wait操作(如果empty信号量的值为0则阻塞,直到有空闲位置);消费者从缓冲区取出一个数据项后,会对empty信号量执行sem_post操作(增加信号量的值)。

  • 生产者线程(producer)

    • 生产者线程的主要任务是生成随机数,并将其放入共享缓冲区。

    • while(1)循环中:

      • 首先生成一个0到99之间的随机数item

      • 然后执行sem_wait(&empty),如果缓冲区没有空闲位置(empty信号量的值为0),则生产者线程阻塞在这里,直到有空闲位置。

      • 接着执行sem_wait(&mutex)获取对缓冲区的互斥访问权。

      • 将生成的item放入缓冲区buffer[in],并打印出Producer produced item <item> at position <in>,这里<item>是生成的随机数,<in>是缓冲区的索引位置。

      • 更新in的值为(in + 1)%BUFFER_SIZE,以实现循环使用缓冲区。

      • 释放对缓冲区的互斥访问权sem_post(&mutex),并执行sem_post(&full)表示缓冲区中有了一个新的数据项。

      • 最后执行sleep(1),暂停1秒后再次循环。

  • 消费者线程(consumer)

    • 消费者线程的主要任务是从共享缓冲区中取出数据。

    • while(1)循环中:

      • 首先执行sem_wait(&full),如果缓冲区中没有数据(full信号量的值为0),则消费者线程阻塞在这里,直到有数据可供消费。

      • 接着执行sem_wait(&mutex)获取对缓冲区的互斥访问权。

      • 从缓冲区buffer[out]中取出数据项item,并打印出Consumer consumed item <item> from position <out>,这里<item>是取出的数据,<out>是缓冲区的索引位置。

      • 更新out的值为(out + 1)%BUFFER_SIZE,以实现循环使用缓冲区。

      • 释放对缓冲区的互斥访问权sem_post(&mutex),并执行sem_post(&empty)表示缓冲区中有了一个新的空闲位置。

      • 最后执行sleep(2),暂停2秒后再次循环。

  • 缓冲区位置索引(inout

    • 生产者角度(in

      • 在生产者函数中,in用于确定将生产的物品放入缓冲区的位置。当生产者生成一个新的物品(例如随机数)时,它会将这个物品放入buffer[in]的位置。

      • in的初始值为0,表示生产者开始时将物品放入缓冲区的第一个位置。每次成功放入一个物品后,in的值会更新为(in + 1)%BUFFER_SIZE。这里的%BUFFER_SIZE操作实现了循环缓冲区的效果。例如,当BUFFER_SIZE为5时,如果in的值为4,下一次in将变为0,这样就可以循环使用缓冲区的空间,避免了缓冲区溢出的问题。

    • 消费者角度(out

      • 在消费者函数中,out用于确定从缓冲区中取出物品的位置。消费者从buffer[out]位置取出物品。

      • in类似,out的初始值为0,表示消费者开始时从缓冲区的第一个位置取出物品。每次成功取出一个物品后,out的值会更新为(out + 1)%BUFFER_SIZE,同样实现了循环使用缓冲区的效果。

  • 示例说明

    • 假设BUFFER_SIZE = 3

    • 生产者开始生产物品:

      • 首先生产一个物品,放入buffer[0](此时in = 0)。

      • 接着生产下一个物品,放入buffer[1](此时in = 1)。

      • 再生产一个物品,放入buffer[2](此时in = 2)。

      • 当再次生产物品时,in=(2 + 1)%3 = 0,于是这个新物品将被放入buffer[0],覆盖之前在这个位置的物品(如果之前的物品还未被消费)。

    • 消费者开始消费物品:

      • 首先从buffer[0]取出物品(此时out = 0)。

      • 接着从buffer[1]取出物品(此时out = 1)。

      • 再从buffer[2]取出物品(此时out = 2)。

      • 当再次消费物品时,out=(2 + 1)%3 = 0,于是将从buffer[0]取出物品(如果这个位置有新的物品被生产出来)。

 调试过程

 

 结果截图

​​​​​​​

  • Producer produced item 83 at position 0

    • 这表明生产者线程成功生成了一个随机数83,并将其放入了缓冲区的位置0。这一操作过程是:生产者首先通过sem_wait(&empty)检查到缓冲区有空位(因为初始时empty信号量的值为BUFFER_SIZE),然后通过sem_wait(&mutex)获取对缓冲区的互斥访问权,将83放入buffer[0],最后更新in为1(下一次将放入位置1),并释放互斥访问权和更新full信号量。

  • Consumer consumed item 83 from position 0

    • 这表明消费者线程从缓冲区的位置0成功取出了数据项83。这一操作过程是:消费者首先通过sem_wait(&full)检查到缓冲区有数据(因为生产者已经放入了数据并更新了full信号量),然后通过sem_wait(&mutex)获取对缓冲区的互斥访问权,从buffer[0]取出83,最后更新out为1(下一次将从位置1取数据),并释放互斥访问权和更新empty信号量。

使用有名信号量的代码

        无名信号量和有名信号量的区别在于它们的创建和使用方式。无名信号量是通过sem_init()函数初始化的,它是一个普通的整数变量,可以通过指针来访问和操作。而有名信号量是通过sem_open()函数创建的,它通过一个名字来访问和操作,可以在不同的进程之间共享。

        无名信号量通常用于同一进程内的线程之间的同步和互斥,因为它们是在内存中创建的,并且只能被同一进程内的线程访问。而有名信号量则适用于不同进程之间的同步和互斥,因为它们是通过文件系统创建的,可以被多个进程共享和访问。

        在上述代码中,使用了无名信号量来实现生产者消费者问题。如果要使用有名信号量实现相同的功能,需要将代码稍作修改:

  1. 使用sem_open()函数替换sem_init()函数创建有名信号量。

  2. 使用sem_close()函数关闭有名信号量。

  3. 使用sem_unlink()函数删除有名信号量。

#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>

#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int in = 0;
int out = 0;

// 有名信号量
sem_t *mutex;
sem_t *full;
sem_t *empty;

// 生产者函数
void *producer(void *arg) {
    int item;
    while (1) {
        item = rand() % 100;
        sem_wait(empty);
        sem_wait(mutex);

        buffer[in] = item;
        printf("Producer produced item %d at position %d\n", item, in);
        in = (in + 1) % BUFFER_SIZE;

        sem_post(mutex);
        sem_post(full);
        sleep(1);
    }
    return NULL;
}

// 消费者函数
void *consumer(void *arg) {
    int item;
    while (1) {
        sem_wait(full);
        sem_wait(mutex);

        item = buffer[out];
        printf("Consumer consumed item %d from position %d\n", item, out);
        out = (out + 1) % BUFFER_SIZE;

        sem_post(mutex);
        sem_post(empty);
        sleep(2);
    }
    return NULL;
}


int main() {
    pthread_t producer_thread, consumer_thread;

    // 创建有名信号量
    mutex = sem_open("/mutex_sem", O_CREAT, 0666, 1);
    full = sem_open("/full_sem", O_CREAT, 0666, 0);
    empty = sem_open("/empty_sem", O_CREAT, 0666, BUFFER_SIZE);

    // 创建生产者和消费者线程
    pthread_create(&producer_thread, NULL, producer, NULL);
    pthread_create(&consumer_thread, NULL, consumer, NULL);

    // 等待线程结束
    pthread_join(producer_thread, NULL);
    pthread_join(consumer_thread, NULL);

    // 关闭和删除有名信号量
    sem_close(mutex);
    sem_close(full);
    sem_close(empty);
    sem_unlink("/mutex_sem");
    sem_unlink("/full_sem");
    sem_unlink("/empty_sem");


    return 0;
}


 交换互斥和同步的P,V操作顺序时,程序运行结果

         任务顺序的同步机制没有正确执行,新的任务在获取mutex信号量时阻塞,造成死锁情况。

应用场景-火车票售票过程

多个售票窗口同时售票。为了保证每个窗口都能正确地处理售票请求,我们需要确保同一时间只有一个窗口能够访问共享资源(例如票库存)。此外,当票库存为空时,所有窗口都应该停止售票操作,直到新的票被补充到库存中。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>

class TicketSystem {
private:
    int ticket_count;
    std::mutex mutex_ticket;
    std::condition_variable cv;

public:
    TicketSystem(int initial_count) : ticket_count(initial_count) {}

    bool buyTicket() {
        std::unique_lock<std::mutex> lock(mutex_ticket);
        if (ticket_count > 0) {
            ticket_count--;
            std::cout << "成功购买一张票,剩余票数: " + std::to_string(ticket_count) << std::endl;
            return true;
        }
        std::cout << "无票可买" << std::endl;
        return false;
    }

    void refundTicket() {
        std::unique_lock<std::mutex> lock(mutex_ticket);
        ticket_count++;
        std::cout << "成功退票一张,剩余票数: " + std::to_string(ticket_count) << std::endl;
        cv.notify_one();
    }
};

void passenger(TicketSystem& ticket_system) {
    ticket_system.buyTicket();
}

int main() {
    TicketSystem ticket_system(10);

    // 创建多个乘客线程
    std::vector<std::thread> passengers;
    for (int i = 0; i < 15; i++) {
        passengers.push_back(std::thread(passenger, std::ref(ticket_system)));
    }

    // 模拟退票
    ticket_system.refundTicket();

    // 再创建一些乘客尝试购票
    for (int i = 0; i < 3; i++) {
        passengers.push_back(std::thread(passenger, std::ref(ticket_system)));
    }

    // 等待所有线程完成
    for (auto& th : passengers) {
        if (th.joinable()) {
            th.join();
        }
    }

    return 0;
}


运行结果截图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/895180.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【CTF-SHOW】Web入门 Web14 【editor泄露-详】【var/www/html目录-详】

editor泄露问题通常出现在涉及文件编辑器或脚本编辑器的题目中&#xff0c;尤其是在Web安全或Pwn&#xff08;系统漏洞挖掘&#xff09;类别中。editor泄露的本质是由于系统未能妥善处理临时文件、编辑历史或进程信息&#xff0c;导致攻击者可以通过某种途径获取正在编辑的敏感…

CABiNet:用于低延迟语义分割的高效上下文聚合网络

摘要 随着自主机器需求的不断增加&#xff0c;视觉场景理解的像素级语义分割不仅需要准确&#xff0c;而且需要高效&#xff0c;以满足任何潜在的实时应用需求。在本文中&#xff0c;我们提出了CABiNet&#xff08;Context Aggregated Bi-lateral Network&#xff0c;上下文聚…

力扣3191.使二进制数全变成1

给你一个二进制数组 nums 。 你可以对数组执行以下操作 任意 次&#xff08;也可以 0 次&#xff09;&#xff1a; 选择数组中 任意连续 3 个元素&#xff0c;并将它们 全部反转 。 反转 一个元素指的是将它的值从 0 变 1 &#xff0c;或者从 1 变 0 。 请你返回将 nums 中…

Unity Spine优化思路

最近终于闲下来了&#xff0c;于是开始把近期探索到的unity相关优化整理起来。 我们的项目采用的人物表现方式是spine动画&#xff0c;这在2D游戏里算比较常见的解决方案了&#xff0c;但是里面有一些设置需要提前注意一下&#xff0c;否则会造成不必要的性能浪费。 养成读官…

SQL Injection | SQL 注入概述

关注这个漏洞的其他相关笔记&#xff1a;SQL 注入漏洞 - 学习手册-CSDN博客 0x01&#xff1a;SQL 注入漏洞介绍 SQL 注入就是指 Web 应用程序对用户输入数据的合法性没有判断&#xff0c;前端传入后端的参数是可控的&#xff0c;并且参数会带入到数据库中执行&#xff0c;导致…

LabVIEW自动化流动返混实验系统

随着工业自动化的不断发展&#xff0c;连续流动反应器在化工、医药等领域中的应用日益广泛。传统的流动返混实验操作复杂&#xff0c;数据记录和处理不便&#xff0c;基于LabVIEW的全自动流动返混实验系统能自动测定多釜反应器、单釜反应器和管式反应器的停留时间分布&#xff…

pytest框架的allure报告怎么去看

pytest框架的allure报告怎么去看 一、安装jdk和allure1.1安装jdk&#xff08;自行找资料&#xff09;1.2安装Allure 二、编写pytest代码三、执行脚本3.1 运行测试并生成 Allure 结果3.2 你可以使用以下命令来查看生成的报告3.3生成的视图 一、安装jdk和allure 1.1安装jdk&…

LabVIEW提高开发效率技巧----VI继承与重载

在LabVIEW开发中&#xff0c;继承和重载是面向对象编程&#xff08;OOP&#xff09;中的重要概念。通过合理运用继承与重载&#xff0c;不仅能提高代码的复用性和灵活性&#xff0c;还能减少开发时间和维护成本。下面从多个角度介绍如何在LabVIEW中使用继承和重载&#xff0c;并…

机器学习建模分析

机器学习 5.1 机器学习概述5.1.1 机器学习与人工智能5.1.2 python机器学习方法库 5.2 回归分析5.2.1 回归分析原理5.2.2 回归分析实现 5.3 分类分析5.3.1 分类学习原理5.3.2 决策树5.5.3 支持向量机 5.4 聚类分析5.4.1 聚类任务5.4.2 K-means算法 5.5 神经网络和深度学习5.5.1神…

YOLO11来啦 | 详细解读YOLOv8的改进模块!

简介 2024年可谓是YOLO历史性的一年&#xff0c;9月份的最后一天迎来了YOLO2024年的第三部巨作。2024年2月21日&#xff0c;继 2023 年 1 月 YOLOv8 正式发布一年多以后&#xff0c;YOLOv9 才终于到来了&#xff01;YOLOv9提出了可编程梯度信息&#xff08;Programmable Gradi…

msql事务隔离级别 线上问题

1. 对应代码 解决方式&#xff1a; 在事务隔离级别为可重复读&#xff08;RR&#xff09;时&#xff0c;数据库确实通常会记录当前数据的快照。 在可重复读隔离级别下&#xff0c;事务在执行期间看到的数据是事务开始时的数据快照&#xff0c;即使其他事务对数据进行了修改&am…

Artistic Oil Paint 艺术油画着色器插件

只需轻轻一点&#xff0c;即可将您的视频游戏转化为艺术品&#xff01;&#xff08;也许更多…&#xff09;。 ✓ 整个商店中最可配置的选项。 ✓ 六种先进算法。 ✓ 细节增强算法。 ✓ 完整的源代码&#xff08;脚本和着色器&#xff09;。 ✓ 包含在“艺术包”中。 &#x1f…

读人工智能全传16读后总结与感想兼导读

读人工智能全传16读后总结与感想兼导读.png 1. 基本信息 人工智能全传 [英]迈克尔伍尔德里奇 著 浙江科学技术出版社,2021年3月出版 1.1. 读薄率 书籍总字数234千字&#xff0c;笔记总字数46186字。 读薄率46186234000≈19.7% 1.2. 读厚方向 千脑智能脑机穿越未来呼啸而来虚拟人…

Redis知识应用索引指南

Redis&#xff0c;全称为Remote Dictionary Server&#xff0c;是一个开源的高性能键值对数据库。它以其卓越的性能、丰富的数据结构和灵活的持久化机制&#xff0c;在现代应用中扮演着至关重要的角色 1 什么是redis Redis是一个使用ANSI C语言编写的开源、跨平台的键值存储系…

ubuntu18开启ssh服务

本部分主要是记述下如何打开ubuntu的ssh功能&#xff0c;从而可以以ssh的方式远程连接ubuntu&#xff0c;而不需要每次都在现场工作。 1. 更新源 $ apt update [sudo apt update] 2. 下载openssh-server # sudo apt install openssh-server 3. 看下服务状态 # service s…

[C#][winform]基于yolov8的道路交通事故检测系统C#源码+onnx模型+评估指标曲线+精美GUI界面

【重要说明】 该系统以opencvsharp作图像处理,onnxruntime做推理引擎&#xff0c;使用CPU进行推理&#xff0c;适合有显卡或者没有显卡windows x64系统均可&#xff0c;不支持macOS和Linux系统&#xff0c;不支持x86的windows操作系统。由于采用CPU推理&#xff0c;要比GPU慢。…

kafka自定义配置信息踩坑

org.apache.kafka.common.config.ConfigException: Invalid value 0 for configuration acks: Expected value to be a string, but it was a java.lang.Integer 场景描述&#xff1a; 单个kafka使用springboot框架自带的 yml 配置完全OK&#xff08;因为底层会帮我们处理好类…

重学SpringBoot3-集成Spring Security(一)

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-集成Spring Security&#xff08;一&#xff09; 1. 简介与概念2. 基础配置2.1. 添加依赖2.1 基本认证与授权配置 3. 密码加密3.1. 如何加密用户密码3.2…

区块链技术在网络安全中的应用研究

摘要&#xff1a; 随着网络技术的快速发展&#xff0c;网络安全问题日益凸显。区块链技术以其去中心化、不可篡改、可追溯等特性&#xff0c;为网络安全提供了新的解决方案。本文深入探讨了区块链技术在网络安全多个领域的应用&#xff0c;包括数据加密与存储、身份认证、网络攻…

Linux 防火墙的开启、关闭、禁用命令

Linux 防火墙的开启、关闭、禁用命令 文章目录 Linux 防火墙的开启、关闭、禁用命令1.设置开机启用防火墙2.设置开机禁用防火墙3.启动防火墙4.关闭防火墙5.检查防火墙状态 1.设置开机启用防火墙 systemctl enable firewalld.service2.设置开机禁用防火墙 systemctl disable f…