!!!本文严格按照实验报告规范编写。如果只需要代码详情,可以直接参考代码部分
项目地址:ReaderAndWriter(希望动手动脚,点个star)
内容
二、外形设计
三、详细设计
四、调试分析
五、使用说明
六、测试运行结果
七、源码
一、需求分析(一),实验相关概念
在一些进程并发运行的过程中,可能会频繁访问共享资源,因此不同的读写执行顺序一般会导致不同的结果。换句话说,并发通常会导致数据同步问题。
其中最著名的问题之一是读写器问题,其定义如下:
有一个数据区(临界区)被多个线程共享。有的进程(读进程)只读取这个数据区的数据读者写者问题流程图,有的进程(写进程)只把数据写入数据区。当两个或多个读进程同时访问共享数据时没有副作用,但如果一个写进程和其他进程(读进程或写进程)同时访问共享数据,可能会导致数据不一致错误(数据同步问题)。因此要求:
即读进程不需要排除其他读进程,写进程需要排除所有其他进程,包括读进程和写进程。
(二),实验任务总结
在读写器问题中,有多种解决方案:读者优先、作者优先和公平竞争。
在阅读器优先策略中,阅读进程具有优先权读者写者问题流程图,即当至少有一个阅读进程正在阅读时,后续阅读进程无需等待,可以直接进入;在这个过程中,写进程会被阻塞,直到所有读进程都读完(写进程可能会饿死)。
本实验主要针对读者优先级的解决方案,利用多线程模拟读者和写者对临界区的互斥访问。
(二),测试数据要求
该实验的测试数据保存在一个文件中。
测试数据文件包括n行测试数据,分别描述了创建的n个线程是读写器,读写操作的开始时间和持续时间。
每行测试数据包括四个字段,以空格分隔:
测试数据是随机生成的,为简单起见,假设每个线程只执行一次读或写操作,之后运行结束。
下面是一个测试数据文件的例子:
为了排版方便,用表格表示。这些行中的每一行对应于文件中的一行。
1R352R453W52…………
二、大纲设计(一),抽象数据类型
在并发问题上,关键是解决互斥问题。在互斥解决方案中,通常使用信号量和监视器等工具。
在这个实验中,我使用了信号量,一个不需要忙等待的同步互斥工具。由于要实现互斥功能,所以对应的信号量要初始化为1。
从一开始的需求分析可以看出,在读写器问题的临界区的使用中,不存在读写器之间的互斥问题,不存在写器与写器之间的互斥问题,并且存在读写器问题。也是作家和读者之间的互斥问题。存在互斥问题。所以需要设计一个信号量wr来实现一个写者和其他读者或写者之间的互斥。
至此,基于信号量的读写器问题的抽象数据类型定义完成。这种抽象类型可用于读者优先策略和编写者优先策略。
(二),程序实现流程三、详细设计(一),读者优先策略实现
在本实验中,由于采用了读者优先策略,因此需要对该策略进行进一步的详细设计。
在阅读器优先策略中,当至少有一个阅读进程在阅读时,后续的阅读进程不需要等待,因此需要设计一个变量readCount来记录阅读器的数量。
当读者进入临界区时,readCount 自动加 1;当有reader离开临界区时,readCount自动减1。因此,当readCount为1时,表示第一个reader进入临界区,可以锁定临界区(即信号量wr递减) ,使得后续的写者无法进入临界区,后续的阅读过程也不需要锁定临界区,可以直接进入临界区;只有当所有读者都离开时,即readCount为0时,临界区的锁才被释放(即信号量wr自增),阻塞的写者才能进入。临界区和锁定操作。
虽然临界区的读取器和读取器之间不存在互斥问题,但是当多个读取器修改 readCount 变量时,可能会导致数据同步问题。因此,还应设计信号量 x 以确保对 readCount 变量的互斥访问。
流程图如下(可能比较难理解,是多线程操作):
实现上述操作的伪代码如下(使用semWait和semSignal模拟加锁和解锁操作):
11. Reader(){ 22. semWait x 33. readcount++ 44. if readcount == 1 55. semWait wr 66. semSignal x 77. read() //读操作 88. semWait x 99. readcount-- 1010. if readcount == 0 1111. semSignal wr 1212. semSignal x 1313. } 1414. 1515. Writer(){ 1616. semWait wr 1717. write() //写操作 1818. semSignal wr 1919. } 20 21
在这个实验中,我使用java语言来实现代码。由于之前学过java并发编程,所以知道java中有一个现成的semaphore类Semaphore(Semaphore是synchronized的增强版,用来控制并发线程数。——摘自百度)。它封装了基本的信号量操作,使用起来非常方便。由于实验的重点是实现读写器问题,所以不需要自己定义和封装信号量类,所以打算直接引用这个类,方便后面代码的实现。
因此,所有全局变量定义如下(java代码):
11. private final Semaphore x = new Semaphore(1); 22. private final Semaphore wr = new Semaphore(1); 33. private int readCount = 0; 4 5
(二),其他功能,方法实现(太基础,代码省略)
在reader类和writer类的实现中,要实现Runnable接口,重写run方法(在run方法中具体实现reader或writer的读写操作),这样可以使用多个线程来模拟reader、writer进程。
由于本实验需要读取测试数据文件,并根据每一行的数据生成对应的线程,所以每个线程都应该包含线程的序号、临界区的应用运行时间、临界区的执行时间。临界区。所以你应该把这三个变量绑定到每个线程(作为类的成员变量),并在线程中使用Thread.sleep()函数来模拟读、写和请求操作的时间延迟。
另外,实验还要求在每个线程创建、发出读写操作请求、开始读写操作、结束读写操作时显示一行提示信息,也可以很方便的实现。
四、调试分析(一),调试过程
在测试了几组数据后,我发现了一个与我想象的结果不一致的问题:
也就是说,当一个作家在写作时,有作家和读者在后面等着。一直以为当writer完成操作释放信号量wr后,下一个访问临界区的一定是reader(毕竟这叫reader优先策略),所以一直在检查前面的步骤分析查阅数据(可惜百度没有这方面的数据)。
最后咨询了老师,然后才知道下一个访问临界区的对象就是通过抢占信号量wr来决定下一个访问临界区的对象是谁。
测试文件数据如下:
1 瓦 2 4
2 读 3 5
3 瓦 5 4
4 瓦 8 5
5W 10 3
6R 12 3
测试结果如下:
(二),设计过程中的心得体会
由于之前学过Java并发编程,熟悉多线程同步和互斥,所以这个实验对我来说并没有想象的那么难。
我觉得这个程序唯一的缺陷是所有线程不是同时创建的(应该同时申请操作的线程可能有先后顺序),而是在逐行扫描文件时创建的,所以在实验中这个要求可能无法完美实现(线程创建后,对共享资源的读写请求会延迟相应的时间(以秒为单位)后发出)。每个线程的创建之间可能会有几微秒的延迟,这在文件行数较少的情况下完全可以忽略不计。但是如果文件行数以百万计,这个延迟可能会被放大,第一个线程和最后一个线程的创建时间可能相差几秒钟,这可能会对结果产生影响。以我现在的水平,没有办法完美地解决这个问题。我也通过百度了解到,几乎不可能同时创建一个线程,这是我的一个遗憾。
在实验中,我也发现自己很容易进入思维定势。调试过程中提到过,很容易被文字的字面意思误导。这就需要我反省自己,改正这个坏习惯。
五、使用说明
根据需求分析中的需求创建一个测试数据文件,命名为test.txt,放在源码目录下的src目录下(与主代码同级);或者直接修改源代码目录下src目录下的test.txt文件,输入要测试的数据,然后运行Main程序查看结果(在java中运行)。
六、测试和运行结果(一),第一次测试
测试文件数据如下:
1 读 3 5
2 转 4 5
3 瓦 5 2
4 转 10 3
5W 11 3
6 读 13 4
测试结果如下:
(二),第二次测试
测试文件数据如下:
1 瓦 2 4
2 读 3 5
3 转 5 2
4 瓦 7 4
5R 11 3
测试结果如下:
七、 源代码(一)、信号量、读取器类和写入器类定义
1import java.util.concurrent.Semaphore; 2 3/** 4 * @author 思而常青 5 * @since 2020-08-20 15:30 6 */ 7public class RWProblem { 8 private final Semaphore x = new Semaphore(1); 9 private final Semaphore wr = new Semaphore(1); 10 private int readCount = 0; 11 12 /** 13 * 读者 14 */ 15 class Reader implements Runnable { 16 /** 17 * 线程的序号 18 */ 19 private final String num; 20 /** 21 * 线程操作申请时间 22 */ 23 private final long startTime; 24 /** 25 * 线程操作申请时间 26 */ 27 private final long workTime; 28 29 Reader(String num, long startTime, long workTime) { 30 this.num = num; 31 this.startTime = startTime * 1000; 32 this.workTime = workTime * 1000; 33 System.out.println(num + "号读进程被创建"); 34 } 35 36 /** 37 * 读过程 38 */ 39 private void read() { 40 System.out.println(num + "号线程开始读操作"); 41 try { 42 Thread.sleep(workTime); 43 } catch (InterruptedException e) { 44 e.printStackTrace(); 45 } 46 System.out.println(num + "号线程结束读操作"); 47 } 48 49 @Override 50 51 public void run() { 52 try { 53 Thread.sleep(startTime); 54 System.out.println(num + "号线程发出读操作申请"); 55 x.acquire(); 56 readCount++; 57 if (readCount == 1) { 58 wr.acquire(); 59 } 60 x.release(); 61 read(); //读过程 62 x.acquire(); 63 readCount--; 64 if (readCount == 0) { 65 wr.release(); 66 } 67 x.release(); 68 } catch (InterruptedException e) { 69 e.printStackTrace(); 70 } 71 } 72 } 73 74 /** 75 * 写者 76 */ 77 class Writer implements Runnable { 78 79 private final String num; //线程的序号 80 private final long startTime; //线程操作申请时间 81 private final long workTime; //线程的执行时间 82 83 Writer(String num, long startTime, long workTime) { 84 this.num = num; 85 this.startTime = startTime * 1000; 86 this.workTime = workTime * 1000; 87 System.out.println(num + "号写进程被创建"); 88 89 } 90 91 /** 92 * 写过程 93 */ 94 private void write() { 95 System.out.println(num + "号线程开始写操作"); 96 try { 97 Thread.sleep(workTime); 98 } catch (InterruptedException e) { 99 e.printStackTrace(); 100 } 101 System.out.println(num + "号线程结束写操作"); 102 } 103 104 @Override 105 public void run() { 106 try { 107 Thread.sleep(startTime); 108 System.out.println(num + "号线程发出写操作申请"); 109 wr.acquire(); 110 write(); //写过程 111 wr.release(); 112 } catch (InterruptedException e) { 113 e.printStackTrace(); 114 } 115 } 116 } 117} 118 119
(二),主程序(读取文件等)
1import java.io.BufferedReader; 2import java.io.FileReader; 3 4/** 5 * @author 思而常青 6 * @since 2020-08-20 15:36 7 */ 8public class Main { 9 public static void main(String[] args) { 10 RWProblem readerAndWriter = new RWProblem(); 11 String filepath = "src/test.txt"; 12 try (BufferedReader br = new BufferedReader(new FileReader(filepath))) { 13 String line; 14 while ((line = br.readLine()) != null) { 15 String[] words = line.split(" "); 16 //线程数 17 String num = words[0]; 18 //线程类型 19 String type = words[1]; 20 //线程操作申请时间 21 long startTime = Long.parseLong(words[2]); 22 //线程的执行时间 23 long workTime = Long.parseLong(words[3]); 24 if ("R".equals(type)) { 25 RWProblem.Reader reader = readerAndWriter.new Reader(num, startTime, workTime); 26 new Thread(reader).start(); 27 28 } else if ("W".equals(type)) { 29 RWProblem.Writer writer = readerAndWriter.new Writer(num, startTime, workTime); 30 new Thread(writer).start(); 31 } else { 32 System.out.println("测试文件出错"); 33 throw new Exception(); 34 } 35 } 36 } catch (Exception e) { 37 e.printStackTrace(); 38 } 39 } 40} 41 42
项目地址:ReaderAndWriter(希望动手动脚,点个star)
请支持我们!!!