操作系统实验:进程同步控制

文章目录

前言

一、开发语言及实验平台或实验环境

二、实验目的

三、实验要求

四、实验原理

五、实验过程

六、代码详解

七、diy一下

总结


前言

计算机操作系统是一门研究计算机系统的基本原理和设计方法的课程,它涉及到计算机系统的结构、功能、性能和管理等方面。操作系统实验是操作系统课程的重要组成部分,它可以帮助加深对操作系统理论知识的理解,提高分析和解决实际问题的能力,培养动手实践的能力和创新精神。

进程同步控制是操作系统实验中的一个重要内容,它主要涉及到进程间的协作、竞争和互斥等问题,以及如何使用信号量、管程、消息传递等机制来实现进程同步控制的方法。本实验旨在让学生通过编写和运行一些典型的进程同步控制程序,掌握进程同步控制的基本概念、原理和技术,熟悉操作系统提供的进程同步控制接口和工具,增强对操作系统内核功能的认识和理解。


一、开发语言及实验平台或实验环境

C++/JAVA

Turbo C / Microsoft Visual Studio 6.0 /  Microsoft Visual Studio .NET 2010

本文中使用的语言是Java,使用的平台是idea

二、实验目的

(1)加强对进程概念的理解,尤其是对进程的同步与互斥机制的理解

(2)分析进程竞争资源的现象,学习解决进程互斥与同步的方法。

三、实验要求

(1)理解利用进程控制机制;

(2)理解利用信号量进行进程同步控制原理;

(3)使用某种编程语言进行模拟实现生产者-消费者进程。

四、实验原理

(注意:这个仅是个例子,仅供参考)

生产者-消费者问题描述的是:有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为使生产者进程与消费者进程能够并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者进程将它所生产的产品放入一个缓冲区中;消费者进程可以从一个缓冲区中取走产品去消费。尽管所有的生产者和消费者进程都是以异步方式运行的,但它们之间必须保持同步,即不允许消费者进程到一个空缓冲区去取产品;也不允许生产者进程向一个已经装满产品的缓冲区中投放产品。

这是一个同步与互斥共存的问题。

生产者—消费者问题是一个同步问题。即生产者和消费者之间满足如下条件:

(1) 消费者想接收数据时,有界缓冲区中至少有一个单元是满的。

(2) 生产者想发送数据时,有界缓冲区中至少有一个单元是空的。

五、实验过程

浅画一个流程图:

示例代码如下:

class Q
{
String name;
int num=0;
int size=10;
}

class Producer implements Runnable
{
Q q;
Producer(Q q)
{
this.q = q;
this.q.name="producer";
}

public void run()
{
while(true)
{

synchronized(q)
{
if(q.num<q.size)
{
q.num++;
System.out.println("producer已生产第:"+q.num+"个产品!");
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
q.notify();
}
else
{
try {
System.out.println("producer stop!");
q.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}


class Consumer implements Runnable
{
Q q;
Consumer(Q q)
{
this.q = q;
this.q.name="consumer";
}

public void run()
{
while(true)
{
synchronized(q)
{
if(q.num>0)
{
System.out.println("consumer要消费第:"+q.num+"个产品!");
q.num--;
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
q.notifyAll();
}
else
{
try {
System.out.println("consumer stop!");
q.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class project
{
public static void main(String[] args)
{
Q q = new Q();
new Thread(new Producer(q)).start();
new Thread(new Consumer(q)).start();
}
}

效果如下:

六、代码详解

  这段代码定义了三个类:QProducerConsumer

Q类定义了一个名为name的字符串变量,一个名为num的整数变量,初始值为0,以及一个名为size的整数变量,初始值为10。

Producer类实现了Runnable接口,它有一个构造函数,该构造函数接受一个Q类型的参数。在构造函数中,它将传递的参数赋值给成员变量q,并将其成员变量name设置为“producer”。

Producer类中,还定义了一个名为run()的方法。这个方法是一个无限循环,在循环中,它使用synchronized(q)块来确保一次只有一个线程可以访问共享的Q对象。在synchronized(q)块中,它首先检查共享的Q对象中的变量num是否小于其变量size。如果是,则将其变量num增加1,并打印一条消息表示生产了一个产品。然后它调用线程休眠100毫秒,并使用q.notify()唤醒等待该对象锁的线程。

如果共享的

Q对象中的变量num不小于其变量size,则打印一条消息表示生产者停止,并调用q.wait()使当前线程等待,直到其他线程调用该对象的notify()notifyAll()方法。

Consumer类也实现了Runnable接口,它有一个构造函数,该构造函数接受一个Q类型的参数。在构造函数中,它将传递的参数赋值给成员变量q,并将其成员变量name设置为“consumer”。

Consumer类中,也定义了一个名为run()的方法。这个方法也是一个无限循环,在循环中,它也使用synchronized(q)块来确保一次只有一个线程可以访问共享的Q对象。在synchronized(q)块中,它首先检查共享的Q对象中的变量num是否大于0。如果是,则打印一条消息表示要消费一个产品,并将其变量num减少1。然后它调用线程休眠100毫秒,并使用

q.notifyAll()唤醒等待该对象锁的所有线程。

如果共享的Q对象中的变量num不大于0,则打印一条消息表示消费者停止,并调用q.wait()使当前线程等待,直到其他线程调用该对象的notify()notifyAll()方法。

最后,project类定义了一个名为main()的方法。在这个方法中,它创建了一个新的Q对象,并使用这个对象创建了一个新的Producer对象和一个新的Consumer对象。然后它分别为这两个对象创建了一个新的线程,并启动了这两个线程。

七、diy一下

改成信号量机制:

import java.util.concurrent.Semaphore;

public class os1_1_1 {
    static class Q {
        String name;
        int num = 0;
        int size = 10;
        Semaphore empty = new Semaphore(size);
        Semaphore full = new Semaphore(0);
        Semaphore mutex = new Semaphore(1);
    }

    static class Producer implements Runnable {
        Q q;

        Producer(Q q) {
            this.q = q;
            this.q.name = "producer";
        }

        public void run() {
            while (true) {
                try {
                    q.empty.acquire();
                    q.mutex.acquire();
                    q.num++;
                    System.out.println("producer已生产第:" + q.num + "个产品!");
                    Thread.currentThread().sleep(100);
                    q.mutex.release();
                    q.full.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class Consumer implements Runnable {
        Q q;

        Consumer(Q q) {
            this.q = q;
            this.q.name = "consumer";
        }

        public void run() {
            while (true) {
                try {
                    q.full.acquire();
                    q.mutex.acquire();
                    System.out.println("consumer要消费第:" + q.num + "个产品!");
                    q.num--;
                    Thread.currentThread().sleep(100);
                    q.mutex.release();
                    q.empty.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

        public static void main(String[] args) {
            Q q = new Q();
            new Thread(new Producer(q)).start();
            new Thread(new Consumer(q)).start();
        }

}

这段代码中,我们使用了三个信号量:emptyfullmutexempty表示空缓冲区的数目,初始值为有界缓冲区的大小,即sizefull表示已用缓冲区的数目,初始值为0。mutex是一个互斥信号量,用于确保生产者和消费者线程之间的互斥执行,初始值为1。

在生产者线程中,我们首先调用q.empty.acquire()来获取信号量。如果成功获取信号量,则调用q.mutex.acquire()来获取互斥信号量。如果成功获取互斥信号量,则执行生产操作。完成生产操作后,我们调用q.mutex.release()来释放互斥信号量,并调用q.full.release()来释放消费者信号量,以便消费者线程可以执行消费操作。

在消费者线程中,我们首先调用q.full.acquire()来获取信号量。如果成功获取信号量,则调用q.mutex.acquire()来获取互斥信号量。如果成功获取互斥信号量,则执行消费操作。完成消费操作后,我们调用q.mutex.release()来释放互斥信号量,并调用q.empty.release()来释放生产者信号量,以便生产者线程可以执行生产操作。

看看效果:

改成c语言形式:

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

#define SIZE 10

typedef struct {
    char *name;
    int num;
    int size;
    sem_t empty;
    sem_t full;
    sem_t mutex;
} Q;

void *producer(void *arg) {
    Q *q = (Q *)arg;
    while (1) {
        sem_wait(&q->empty);
        sem_wait(&q->mutex);
        q->num++;
        printf("producer已生产第:%d个产品!\n", q->num);
        sleep(1);
        sem_post(&q->mutex);
        sem_post(&q->full);
    }
}

void *consumer(void *arg) {
    Q *q = (Q *)arg;
    while (1) {
        sem_wait(&q->full);
        sem_wait(&q->mutex);
        printf("consumer要消费第:%d个产品!\n", q->num);
        q->num--;
        sleep(1);
        sem_post(&q->mutex);
        sem_post(&q->empty);
    }
}

int main() {
    Q q = {.name = "Q", .num = 0, .size = SIZE};
    sem_init(&q.empty, 0, SIZE);
    sem_init(&q.full, 0, 0);
    sem_init(&q.mutex, 0, 1);

    pthread_t producer_thread, consumer_thread;
    pthread_create(&producer_thread, NULL, producer, &q);
    pthread_create(&consumer_thread, NULL, consumer, &q);

    pthread_join(producer_thread, NULL);
    pthread_join(consumer_thread, NULL);

    return 0;
}

这段代码中,我们定义了一个名为Q的结构体,它包含了一个名为name的字符串指针,一个名为num的整数变量,一个名为size的整数变量,以及三个信号量:emptyfullmutex

在主函数中,我们初始化了一个Q类型的变量,并使用sem_init()函数初始化了它的三个信号量。然后我们创建了两个线程:生产者线程和消费者线程,并分别调用了producer()consumer()函数。

在生产者线程中,我们首先调用sem_wait()函数来获取信号量。如果成功获取信号量,则调用sem_wait()函数来获取互斥信号量。如果成功获取互斥信号量,则执行生产操作。完成生产操作后,我们调用sem_post()函数来释放互斥信号量,并调用sem_post()函数来释放消费者信号量,以便消费者线程可以执行消费操作。

在消费者线程中,我们首先调用sem_wait()函数来获取信号量。如果成功获取信号量,则调用sem_wait()函数来获取互斥信号量。如果成功获取互斥信号量,则执行消费操作。完成消费操作后,我们调用sem_post()函数来释放互斥信号量,并调用sem_post()函数来释放生产者信号量,以便生产者线程可以执行生产操作。


总结

这篇文章中所述的实验是从网页内容中总结的。它是一个关于进程同步控制的实验,目的是让学生理解进程同步的概念和方法,以及掌握java或c语言的多线程编程技巧。实验要求学生模拟一个生产者-消费者问题,使用信号量机制来实现进程间的同步和互斥。实验设计了一个缓冲区类,一个生产者类,一个消费者类,以及一个主类来控制程序的运行。实验结果显示,程序能够正确地运行,并且没有出现死锁或数据丢失的情况。

猜你喜欢

转载自blog.csdn.net/m0_72471315/article/details/129865850