操作系统进程同步实验报告

华侨大学计算机科学与技术学院

操作系统实验报告

进程同步

课程名称:       操作系统实验                       

实验项目名称:        进程同步                      

学    院:    计算机科学与技术学院               

专业班级:                                                    

姓    名:                                                       

学    号:                                                       

目录

1.描述及需求分析... 2

1.1 实验目标... 2

1.2 问题描述... 3

1.3实验要求... 3

1.4 输入形式... 3

1.5 输出形式... 3

2实验设计... 3

3. 无信号量实现线程互斥... 4

3.1使用类以及变量的说明... 4

3.2包含的核心方法及函数解析... 4

3.3 实验结果与分析... 6

4 互斥信号量mutex实现线程互斥... 6

4.1 使用类以及变量的说明... 6

4.2 包含的核心方法及函数解析... 7

4.3 实验结果与分析... 8

5 同步信号量full empty实现线程同步... 8

5.1 实验设计... 8

5.2 使用类以及变量的说明... 9

5.3 包含的核心函数解析... 9

5.4 实验结果与分析... 10

6 同时使用信号量实现生产者-消费者的互斥与同步... 10

6.1设计思想... 10

6.2 使用类以及变量的说明... 11

6.3 包含的核心方法及函数解析... 11

6.4 实验结果与分析... 13

7实验总结... 13

8 附录... 14

无信号量实现线程互斥... 14

互斥信号量mutex实现线程互斥... 17

同步信号量full empty 实现线程同步... 18

同时使用信号量实现生产者-消费者的互斥与同步... 20

1.描述及需求分析

1.1 实验目标

1.  理解进程同步的两种制约关系:互斥与同步。

2.  掌握利用记录型信号量解决进程同步问题的方法。

3.  加深对进程同步控制的理解。

1.2 问题描述

以生产者-消费者模型为基础,在Windows环境下创建一个控制台进程(或者界面进程),在该进程中创建读者写者线程模拟生产者和消费者。写者线程写入数据,然后将数据放置在一个空缓冲区中供读者线程读取。读者线程从缓冲区中获得数据,然后释放缓冲区。当写者线程写入数据时,如果没有空缓冲区可用,那么写者线程必须等待读者线程释放出一个空缓冲区。当读者线程读取数据时,如果没有满的缓冲区,那么读入线程将被阻塞,直到新的数据被写进去。

1.3实验要求

设计并实现一个进程,该进程拥有一个生产者线程和一个消费者线程,它们使用N个不同的缓冲区(N为一个确定的数值,本实验中取N=16)。可以作如下的实验尝试,并观察和记录进程同步效果:

  • 没有信号量时实现生产者线程与消费者线程互斥制约。
  • 用以下信号量实现生产者线程与消费者线程互斥制约:

一个互斥信号量mutex,用以阻止生产者线程和消费者线程同时操作缓冲区列表。

  • 用以下信号量实现生产者线程与消费者线程的同步制约:

一个信号量full,当生产者线程生产出一个物品时可以用它向消费者线程发出信号。

一个信号量empty,消费者线程释放出一个空缓冲区时可以用它向生产者线程发出信号。

  • 同时使用(2)(3)中的信号量实现生产者-消费者的互斥与同步制约。

1.4 输入形式

1.5 输出形式

【线程名】生产/消费一个产品,现库存

2实验设计

生产者、消费者共享一个初始为空、大小为max_size=16的缓冲区。

只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待(同步)

只有缓冲区不空时,消费者才能从中取出产品,否则必须等待(同步)

缓冲区是临界资源,各进程必须互斥地访问。(互斥)

3. 无信号量实现线程互斥

  

3.1使用类以及变量的说明

Producer生产者类:定义线程任务类Producer实现Runnable接口,含Storage对象成员变量、无参构造、有参构造,重写run方法,run方法做的事情是线程启动时,会自动循环执行休眠一秒,调用Storage对象的produce方法,生产一个产品的操作。

Consumer消费者类:定义线程任务类Consumer实现Runnable接口,含Storage对象成员变量、无参构造,有参构造,重写run方法,run方法做的事情是线程启动时,会自动循环执行休眠一秒,调用Storage对象的consume方法,消费产品的操作。

Storage仓库类:声明两个私有成员变量:仓库最大容量MAX_SIZE = 16和仓库的存储载体数据结构为链表LinkedList,产品类型用Object模拟。该类提供了了consume和produce方法,以便上述两个任务类调用。

测试类Main:以有参构造器的方式初始化 Producer和Consumer任务对象,将任务对象交给Thread线程类处理,调用线程对象start方法启动线程。

3.2包含的核心方法及函数解析

Producer类的run()方法

当使用对象实现接口 Runnable 来创建线程时,启动该线程会导致在该单独执行的线程中调用对象的 run 方法,自动循环执行休眠一秒,调用Storage对象的produce方法,生产一个产品的操作。

Consumer类的run()方法

当使用对象实现接口 Runnable 来创建线程时,启动该线程会导致在该单独执行的线程中调用对象的 run 方法,自动循环执行休眠一秒,调用Storage对象的consume方法,消费产品的操作。

Storage类的produce()方法

如果仓库里再放一个产品会大于仓库的最大容量16的话,说明此时仓库已满,这时给出提示信息:生产者线程名仓库已满,调用父类Object的wait方法,将该线程放入阻塞队列,直到它被唤醒。当跳出while循环时,说明仓库还没有放满产品,满足生产的条件,执行list对象的 add方法,给出提示信息:生产者线程名生产一个产品,现库存为list.size,完成这些后list调用notify方法向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。

Storage类的consume()方法

如果仓库容量为0的话,说明此时仓库已空,这时给出提示信息:消费者线程名仓库为空,调用父类Object的wait方法,将该线程放入阻塞队列,直到它被唤醒。当跳出while循环时,说明有库存,满足消费的条件,执行list对象的 remove方法,给出提示信息:消费者线程名消费一个产品,现库存为list.size,完成这些后list调用notify方法向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。

刚开始仓库容量为0,若消费者线程先抢到锁对象list(此处共享资源作为锁对象),消费者线程顺利进入同步代码块,发现list.size长度为0,消费者线程自动释放锁,进入阻塞队列,等待被唤醒,此时生产者线程执行,顺利得到锁,判断不满足While条件,跳出,生产一个产品,唤醒其他沉睡的线程:“快起来吧,有产品可以消费了”并释放锁资源。………

当发现仓库的容量为满时,生产者线程给出提示:生产者线程名称仓库已满。然后调用wait方法,释放锁资源,该线程被阻塞,消费者线程顺利拿到锁,开始执行,判断经不满足While条件,跳出While循环,消费掉一个产品, list响应减1,给出提示:“消费者线程名称消费一个产品,现库存”,并唤醒其他线程。

测试类Main:

Thread p1 = new Thread(new Producer(storage));

Thread c1 = new Thread(new Consumer(storage));

p1.start();

c1.start();

‘new’两个任务类,交给两个线程,调用start方法,两个线程同步启动,自动执行为各自量身定制的run方法

生产者线程:睡1秒,生产,循环执行

消费者线程:睡3秒,消费,循环执行


3.3 实验结果与分析

4 互斥信号量mutex实现线程互斥

4.1 使用类以及变量的说明

Storage仓库类声明两个私有成员变量:

仓库最大容量MAX_SIZE = 16

仓库的存储载体数据结构为链表LinkedList

互斥信号量mutex,产品类型用Object模拟

该类提供了了consume和produce方法,以便上述两个任务类调用。

其他三个类和同上,只有库存Storage类不同,下同,不做赘述。

Storage类的produce()方法

如果仓库里再放一个产品小于16的话,说明还可以继续生产,调用add方法向仓库放入生产的产品,打印Log记录:“生产者线程名生产一个产品,现库存”由于要对临界区资源数量进行修改,且max_size=16>1,为了保证操作的原子性,在该操作执行之前,首先对信号量mutex执行P操作,最后执行mutex.release()使mutex资源数量加1

Storage类的consume()方法

如果仓库容量大于0的话,执行list.remove()语句,打印日志:“消费者消费一个产品,现库存为”,在该操作执行之前,首先对信号量mutex执行P操作,在该操作执行之前,首先对信号量mutex执行P操作,最后执行mutex.release()使mutex资源数量加1

4.2 包含的核心方法及函数解析

final Semaphore mutex = new Semaphore(1);

Semaphore信号量类通常用于限制可以访问某些(物理或逻辑)资源的线程数。初始化成员变量,mutex为互斥信号量,初值为1。

mutex.acquire();

从此信号量获取许可,阻塞直到一个信号量可用或线程中断,相当于对mutex执行P操作

mutex.release();

释放许可证,将其返回到信号量,相当于对mutex执行V操作

4.3 实验结果与分析


 

5 同步信号量full empty实现线程同步

5.1 实验设计

Storage仓库类声明五个私有成员变量:

仓库最大容量MAX_SIZE = 16

仓库的存储载体list = new LinkedList()

锁 Lock lock = new ReentrantLock()

仓库满的信号量   Condition full = lock.newCondition()

仓库空的信号量   private final Condition empty = lock.newCondition()

该类提供了了consume和produce方法,以便上述两个任务类调用。

5.2 使用类以及变量的说明

Storage类的produce()方法

如果仓库里再放一个产品会大于仓库的最大容量16的话,说明此时仓库已满,这时给出提示信息:生产者线程名仓库已满,执行full.await();阻塞生产线程。如果ist.size() + 1 < MAX_SIZE),说明仓库还没有放满产品,满足生产的条件,执行list对象的 add方法,给出提示信息:生产者线程名生产一个产品,现库存为list.size,执行empty.signalAll()方法,唤醒empty.signalAll下的所有消费者线程,最后执行lock.unlock释放锁。

Storage类的consume()方法

如果仓库容量为0的话,说明此时仓库已空,这时给出提示信息:消费者线程名仓库为空,调用父类empty.await()方法,阻塞消费线程,直到它被唤醒。while循环条件不满足时,说明有库存,满足消费的条件,执行list对象的 remove方法,给出提示信息:消费者线程名消费一个产品,现库存为list.size,执行full.signalAll()方法,唤醒full.signalAll下的所有生产者线程,最后执行lock.unlock释放锁。

注:在Condition中,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll(),传统线程的通信方式,Condition都可以实现,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。


5.3 包含的核心函数解析

Lock在java.util.concurrent.locks包下,创建的Lock对象,使用lock.lock()获得锁,调用unlock释放锁。其实现提供了比使用方法和语句可以获得的更广泛的锁定操作。它们允许更灵活的结构,可能具有完全不同的属性,并且可能支持多个关联的 Condition 对象。

Condition对象full和empty在java.util.concurrent.locks.Condition包下,可以理解为等待队列的一个管理者,condition确保阻塞的对象按顺序被唤醒。Condition的强大之处在于它可以为多个线程间建立不同的Condition, 使用synchronized/wait()只有一个阻塞队列,notifyAll会唤起所有阻塞队列下的线程,而使用lock/condition,可以实现多个阻塞队列,signalAll只会唤起某个阻塞队列下的阻塞线程。

5.4 实验结果与分析

6 同时使用信号量实现生产者-消费者的互斥与同步

6.1设计思想

图 6-1 生产者消费者问题同步信号量关系

伪代码

Producer(){

       While(1){

              生产一个产品

              P(empty)

              P(mutex)

              把产品放入缓冲区

              V(mutex)

              V(full)

}

}

Consumer(){

       While(1){

              P(full)

              P(mutex)

              从缓冲区消耗一个产品(释放缓冲区)

              V(mutex)

              V(empty)

              消费产品

}

}


6.2 使用类以及变量的说明

图 6-2 Storage类变量声明

该类提供了了consume和produce方法,以便上述两个任务类调用。

6.3 包含的核心方法及函数解析

 首先在放入产品之前判断有无空缓冲区,若empty的值减1后不为负数,说明有空的缓冲区可供生产者生产产品,对mutex异步信号量执行P操作,申请到资源进入临界区,将生产好的产品放入缓冲区,执行list对象的 add方法,给出提示信息:生产者线程名生产一个产品,现库存为list.size,做完这些后,在finally语句中执行mutex.release(),在退出去释放资源,最后full.release(),使得full的值加1,通知消费者已经有产品了,可以来消费了。

若empty的值减1后为负数,说明无多余的缓冲区可供生产者生产产品,生产者线程阻塞,开始执行消费者线程,在确定有产品后(empty-1>=0),对mutex异步信号量执行P操作,消费者开始从缓冲区拿走一个产品,执行list对象的 remove方法,给出提示信息:消费者线程名消费一个产品,在finally语句中执行mutex.release(),在退出去释放资源,最后执行empty.release(),使得empy的值加1,通知生产者已经有一个空的缓冲区了,可以生产了。


6.4 实验结果与分析

7实验总结

对比以上四种方式,我觉得使用异步信号量mutex和同步信号量empty和full解决生产者和消费者问题最优秀!省去了实现进程同步大量的if或者while条件判断,对共享数据的操作只需对信号量执行前P后V的操作。

8 附录

无信号量实现线程互斥

注:以下三个实验代码只有Storage.java不一样,故只贴Srorage.java

互斥信号量mutex实现线程互斥


同步信号量full empty 实现线程同步


 


同时使用信号量实现生产者-消费者的互斥与同步

你可能感兴趣的:(操作系统实验,java)