操作系统之多线程编程—读者优先/写者优先详解

简介: 创建一个包含n 个线程的控制台进程。用这n 个线程来表示n个读者或写者。每个线程按相应测试数据文件的要求,进行读写操作。请用信号量机制分别实现读者优先和写者优先的读者-写者问题。

要求



一、实验目的


1、熟悉多线程编程

2、熟悉使用信号量机制解决进程同步问题


二、实验内容


创建一个包含n 个线程的控制台进程。用这n 个线程来表示n个读者或写者。每个线程按相应测试数据文件的要求,进行读写操作。请用信号量机制分别实现读者优先和写者优先的读者-写者问题。


读者优先:如果一个读者申请进行读操作时已有另一读者正在进行读操作,则该读者可直接开始读操作。

写者优先:如果一个读者申请进行读操作时已有另一写者在等待访问共享资源,则该读者必须等到没有写者处于等待状态后才能开始读操作。


三、实验条件


1、为每个学生提供一台具有WINDOWS 2000/NT/XP操作系统的计算机;

2、实验机器要求安装Visual C 6.0编程平台;

3、实验要求一人一机。


四、 运行结果显示要求


1、要求在每个线程创建、发出读写操作申请、开始读写操作和结束读写操作时分别显示一行提示信息,以确信所有处理都遵守相应的读写操作限制。


2、测试数据文件格式:测试数据文件包括n 行测试数据,分别描述创建的n 个线程是读者还是写者,以及读写操作的开始时间和持续时间。每行测试数据包括四个字段,各字段间用空格分隔。第一字段为一个正整数,表示线程序号。第二字段表示相应线程角色,R 表示读者是,W表示写者。第三字段为一个正数,表示读写操作的开始时间。线程创建后,延时相应时间(单位为秒)后发出对共享资源的读写申请。第四字段为一个正数,表示读写操作的持续时间。当线程读写申请成功后,开始对共享资源的读写操作,该操作持续相应时间后结束,并释放共享资源。下面是一个测试数据文件的例子:


1 R 3 5

2 W 4 5

3 R 5 2

4 R 6 5


理解



上面是实验要求。下面讲讲自己的体会:


  • 首先要明白在多线程编程中的互斥关系——读写互斥写写互斥。两个优先方式都遵从这个出发点。
  • 在满足以上的条件下,进来的线程都好像是在排队,然后看当前谁优先。
  • 自己如果是优先的,那么可以直接插队。再考虑正在执行的是否和自己阻塞,如果正在执行的和自己阻塞,那自己也得排队,只不过排在前面。(类似写者优先的写写线程)如果正在执行的和自己不是阻塞的,那么自己就可以直接进去执行(读者优先的读读进程)。
  • 如果自己不是优先的,那么自己只能老老实实的排队,就算自己前面有和自己不互斥的线程执行也不行。类似写者优先的(读线程在执行,新的写进程等待资源,更新的读线程只能等写进程释放,如果有新的写进程进来,还可以插队,——他只能等待队列中所有在等待的写进程释放完毕再执行自己。)


  • 对于读者优先


读者就是优先的。假设a,b都是同时请求,但是a是读者那么a优先使用资源,还有一点很重要的就是读者优先的读者可以并行执行。而写着只能单线程执行。在执行过程中,只要阻塞的写者在等待过程中有新的读者进来那么他要等待所有读者完成才能自己释放自己。


201812041113177.png


  • 对于写者优先


无疑所有写的操作是优先的,这个过程可能会产生大量阻塞,因为相对较快(本来可以并行的读者被大量阻塞)。如果资源中没有写者那么读者依然可以并行,但是一旦出现写者在等待读者资源,那么新的读者就不能在并行执行,要等待所有写者执行完毕才可执行读者。


20181204112142473.png


对于多线程编程的注意点有:


  1. 读者优先和写者优先是两个不同的策略方法,方法有相似之处但是也有很大不同,函数需要分开完成。


  1. 最主要的排序方式基于时间排序,次要的排序以读者还是写者谁优先为准则


  1. 读者优先或者写者优先的阻塞会导致线程开始时间的变化。而不过采用双队列一个存进入时间的排序,一个存结束时间的排序,修改其中的一个会影响另一个队列中元素值不错,但是如果不对另一个队列进行增/删不会触发堆排序的功能(挺重要的)。


  1. 可能有些阻塞时候的等待时间和开始时间改变处理比较复杂,要考虑当前是读致使阻塞,还是写致使阻塞,还是前面有写的资源再等待致使阻塞。要用多个变量维系系统使得正确的更改线程的阻塞时间。


我的大体思路(水平有限,不一定很准确):


20181204105927991.png


代码


下面附上我的ac代码


package 实验;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Scanner;
public class morethread {
  static Scanner sc=new Scanner(System.in);
  static Queue<thread>q1;
  static Queue<thread>end;
  static thread thread[];
  static int time=0;
  private static void threadcreate(int n) {//创建线程
    thread=new thread[n];
    for(int i=0;i<n;i++) {
    System.out.println("请输入线程"+(i+1)+"的相关属性(id,r/w,intime,timelong)");
    int id=sc.nextInt();
    String type=sc.next();
    int start=sc.nextInt();
    int chixu=sc.nextInt();
    thread [i]=new thread(id, type, start, chixu);
    System.out.println("线程"+id+"  操作:"+type+"  开始时间:"+start+"   持续时间:"+chixu);
    }
  }
  private static void writerthread(int n) {//写者优先
    // TODO 自动生成的方法存根
    q1=new PriorityQueue<thread>(comstart2);
    end=new PriorityQueue<thread>(comend);
    for(int i=0;i<n;i++)
    {
           q1.add(thread[i]);
           end.add(thread[i]);
    }
    int timeend=-1;//读结束的时间最晚
    int writendtime=0;//写操作结束的时间
    thread start=q1.peek();thread finish=end.peek();
    boolean iswait=false;//是否有write操作在等待
    while(!q1.isEmpty()||!end.isEmpty())
    {
      if(!q1.isEmpty()) {start=q1.peek();}
      if(!end.isEmpty()) {finish=end.peek();}
      if(start.starttime<(finish.starttime+finish.totaltime)&&!q1.isEmpty())//可以开始读写操作
      {
        thread team=q1.poll();
        if(team.type.equals("w"))//写操作
        {
          if(team.starttime>=writendtime)//没有写操作在前面
          {
            if(team.starttime<timeend)//需要等待 更改改点信息重新pull入队列
            {
              iswait=true;//声明之后其他读操作就不能再直接进行
              team.starttime=timeend;
              q1.add(team);
              end.remove(team);end.add(team);
            }
            else {//不需要等待,直接开始
              System.out.println("线程"+team.id+" 开始:"+team.type+"操作  时间:"+team.starttime+"  结束时间"+(team.starttime+team.totaltime));
              writendtime=team.starttime+team.totaltime;//更新写操作的结束时间
              iswait=false;//更新等待点
            }
          }
          else//有正在进行的操作,需要等待
          {
            iswait=true;
            team.starttime=writendtime;
            q1.add(team);
            end.remove(team);end.add(team);//移除然后增加才可以重新排序
          }
        }
        else//读操作
        {
          if(iswait)
          {//不清楚他在等待读操作还是写操作,反正它一定不能在下面两个操作之前完成,所以更新节点道值最大的
            int time2=timeend;
            if(time2<writendtime)time2=writendtime;
            team.starttime=time2;
            q1.add(team);
            end.remove(team);end.add(team);
          }
          else//没有等待点
          {
            if(team.starttime<writendtime)//有写操作,读需要等待 将这个点从新弄道队列里面
            {
              team.starttime=writendtime;
              q1.add(team);
              end.remove(team);end.add(team);
            }
            else//可以释放了
            {
            System.out.println("线程"+team.id+" 开始:"+team.type+"操作  时间:"+team.starttime+"  结束时间"+(team.starttime+team.totaltime));  
            if(team.starttime+team.totaltime>timeend)//如果可以更新时间
            {
              timeend=team.starttime+team.totaltime;
            }
            }
          }
        }       
      } 
      else
      {
        thread team=end.poll();
        System.out.println("线程"+team.id+" 结束:"+team.type+" 结束时间"+(team.starttime+team.totaltime));
      }
    }
  }
  private static void readerthread(int n)//读者优先
  {
    q1=new PriorityQueue<thread>(comstart);
    end=new PriorityQueue<thread>(comend);
    for(int i=0;i<n;i++)
    {
           q1.add(thread[i]);
           end.add(thread[i]);
    }
    int timeend=-1;//读结束的时间最晚
    int writendtime=0;//写操作结束的时间,如果有写的状态,那么
    thread start=q1.peek();thread finish=end.peek();
    while(!q1.isEmpty()||!end.isEmpty())
    {
      if(!q1.isEmpty()) {start=q1.peek();}
      if(!end.isEmpty()) {finish=end.peek();}
      if(start.starttime<=(finish.starttime+finish.totaltime)&&!q1.isEmpty())//可以开始读写操作
      {
        thread team=q1.poll();
        if(writendtime>start.starttime)//此时有写的情况,需要等待阻塞,也就是将此队列头抛出修改开始时间然后再次入队
        {
          team.starttime=writendtime;
          q1.add(team);
          end.remove(team);end.add(team);
        }
        else {//此时无写的操作,无操作或者读操作
          //System.out.println("线程"+team.id+" 开始:"+team.type+"操作  时间:"+team.starttime+"  结束时间"+(team.starttime+team.totaltime));
          if(team.type.equals("w"))//写的情况,写需要检查是否阻塞自己如果有读的情况则需要阻塞自己
          {
            int time2=timeend;
            if(time2<writendtime)time2=writendtime;
            if(team.starttime>=time2)//在这个时间前没有任何读或者写的操作,上锁,等于号一定有,因为都抛出它了,他的优先级一定最高
            {
              writendtime=team.starttime+team.totaltime;
              System.out.println("线程"+team.id+" 开始:"+team.type+"操作  时间:"+team.starttime+"  结束时间"+(team.starttime+team.totaltime));
            }
            else {//状态还有读操作,需要阻塞
              team.starttime=time2;
              q1.add(team);
              end.remove(team);end.add(team);
            }
          }
          else//读的情况
          {
            System.out.println("线程"+team.id+" 开始:"+team.type+"操作  时间:"+team.starttime+"  结束时间"+(team.starttime+team.totaltime));
            if(team.starttime+team.totaltime>timeend)
            {
              timeend=team.starttime+team.totaltime;
            }
          }
        }
      }
      else if(!end.isEmpty()){//某个结束操作在这个时间段
        thread team=end.poll();
        System.out.println("线程"+team.id+" 结束:"+team.type+" 结束时间"+(team.starttime+team.totaltime));
      }
    }
  }
  public static void main(String[] args) {
    System.out.println("请输入n个线程程序");    
    int n=sc.nextInt();
    threadcreate(n);
    System.out.println("请输入数字选择读者优先或者写者优先\n1:读者优先\n2:写者优先");
    int index=sc.nextInt();
    while(index<1||index>2) {System.out.println("请输入正确的数字");index=sc.nextInt();}
    if(index==1)//读者优先
    {
      readerthread(n);
    }
    else {
      writerthread(n);
    }
  }
  static Comparator<thread>comstart=new Comparator<thread>() {//读者优先的comparator接口,优先时间,时间相同则先返回
    public int compare(thread o1, thread o2) {
      // TODO 自动生成的方法存根
      if(o1.starttime==o2.starttime)
      {
        return (int)(o1.type.charAt(0)-o2.type.charAt(0));// r w  先r后w
      }
      else
        return o1.starttime-o2.starttime;//返回小根堆      
    }
  };
  static Comparator<thread>comstart2=new Comparator<thread>() {//写者优先的
    public int compare(thread o1, thread o2) {
      // TODO 自动生成的方法存根
      if(o1.starttime==o2.starttime)
      {
        return (int)(o2.type.charAt(0)-o1.type.charAt(0));// w r  先w后r
      }
      else
        return o1.starttime-o2.starttime;//返回小根堆      
    }
  };
  static Comparator<thread>comend=new Comparator<thread>() {
    public int compare(thread o1, thread o2) {
      return (int)((o1.starttime+o1.totaltime)-(o2.starttime+o2.totaltime));
    }
  };
  static class thread
  {
    int id;
    String type;
    int starttime;
    int totaltime;
    public thread(int id,String type,int starttime,int totaltime)
    {
      this.id=id;
      this.type=type;
      this.starttime=starttime;
      this.totaltime=totaltime;
    }
  }
}


测试用例:


读者优先:


20181204110205457.png


写者优先:


20181204110225931.png


本人水平有限,?,如果有大佬发现错了,请指正。


目录
相关文章
|
15天前
|
调度 开发者 Python
深入浅出操作系统:进程与线程的奥秘
在数字世界的底层,操作系统扮演着不可或缺的角色。它如同一位高效的管家,协调和控制着计算机硬件与软件资源。本文将拨开迷雾,深入探索操作系统中两个核心概念——进程与线程。我们将从它们的诞生谈起,逐步剖析它们的本质、区别以及如何影响我们日常使用的应用程序性能。通过简单的比喻,我们将理解这些看似抽象的概念,并学会如何在编程实践中高效利用进程与线程。准备好跟随我一起,揭开操作系统的神秘面纱,让我们的代码运行得更加流畅吧!
|
4月前
|
UED 开发者 Python
探索操作系统的心脏:理解进程与线程
【8月更文挑战第31天】在数字世界的海洋中,操作系统犹如一艘巨轮,其稳定航行依赖于精密的进程与线程机制。本文将揭开这一机制的神秘面纱,通过深入浅出的语言和直观的代码示例,引领读者从理论到实践,体验进程与线程的魅力。我们将从基础概念出发,逐步深入到它们之间的联系与区别,最后探讨如何在编程实践中高效运用这些知识。无论你是初学者还是有经验的开发者,这篇文章都将为你的技术之旅增添新的航标。
|
14天前
|
算法 调度 开发者
深入理解操作系统:进程与线程的管理
在数字世界的复杂编织中,操作系统如同一位精明的指挥家,协调着每一个音符的奏响。本篇文章将带领读者穿越操作系统的幕后,探索进程与线程管理的奥秘。从进程的诞生到线程的舞蹈,我们将一起见证这场微观世界的华丽变奏。通过深入浅出的解释和生动的比喻,本文旨在揭示操作系统如何高效地处理多任务,确保系统的稳定性和效率。让我们一起跟随代码的步伐,走进操作系统的内心世界。
|
1月前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
1月前
深入理解操作系统:进程与线程的管理
【10月更文挑战第30天】操作系统是计算机系统的核心,它负责管理计算机硬件资源,为应用程序提供基础服务。本文将深入探讨操作系统中进程和线程的概念、区别以及它们在资源管理中的作用。通过本文的学习,读者将能够更好地理解操作系统的工作原理,并掌握进程和线程的管理技巧。
44 2
|
1月前
|
调度 Python
深入浅出操作系统:进程与线程的奥秘
【10月更文挑战第28天】在数字世界的幕后,操作系统悄无声息地扮演着关键角色。本文将拨开迷雾,深入探讨操作系统中的两个基本概念——进程和线程。我们将通过生动的比喻和直观的解释,揭示它们之间的差异与联系,并展示如何在实际应用中灵活运用这些知识。准备好了吗?让我们开始这段揭秘之旅!
|
3月前
|
存储 消息中间件 资源调度
「offer来了」进程线程有啥关系?10个知识点带你巩固操作系统基础知识
该文章总结了操作系统基础知识中的十个关键知识点,涵盖了进程与线程的概念及区别、进程间通信方式、线程同步机制、死锁现象及其预防方法、进程状态等内容,并通过具体实例帮助理解这些概念。
「offer来了」进程线程有啥关系?10个知识点带你巩固操作系统基础知识
|
2月前
|
算法 安全 调度
深入理解操作系统:进程与线程的管理
【10月更文挑战第9天】在数字世界的心脏跳动着的,不是别的,正是操作系统。它如同一位无形的指挥家,协调着硬件与软件的和谐合作。本文将揭开操作系统中进程与线程管理的神秘面纱,通过浅显易懂的语言和生动的比喻,带你走进这一复杂而又精妙的世界。我们将从进程的诞生讲起,探索线程的微妙关系,直至深入内核,理解调度算法的智慧。让我们一起跟随代码的脚步,解锁操作系统的更多秘密。
40 1
|
1月前
|
Linux 调度
探索操作系统核心:进程与线程管理
【10月更文挑战第24天】在数字世界的心脏,操作系统扮演着至关重要的角色。它不仅是计算机硬件与软件之间的桥梁,更是管理和调度资源的大管家。本文将深入探讨操作系统的两大基石——进程与线程,揭示它们如何协同工作以确保系统运行得井井有条。通过深入浅出的解释和直观的代码示例,我们将一起解锁操作系统的管理奥秘,理解其对计算任务高效执行的影响。
|
3月前
|
资源调度 算法 调度
深入浅出操作系统之进程与线程管理
【9月更文挑战第29天】在数字世界的庞大舞台上,操作系统扮演着不可或缺的角色,它如同一位精通多门艺术的导演,精心指挥着每一个进程和线程的演出。本文将通过浅显的语言,带你走进操作系统的内心世界,探索进程和线程的管理奥秘,让你对这位幕后英雄有更深的了解。