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

目录

请设计进程既有同步又有互斥的应用场景,并尝试用信号量机制实现。可尝试用有名或无名信号量代码实现上述过程,并给出代码截图、调试过程和运行结果截图。当交换互斥和同步的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;
}


运行结果截图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值