• 操作系统实验——读者写者模型(写优先)


    操作系统实验——读者写者模型(写优先)

    个人博客主页
    参考资料:
    Java实现PV操作 | 生产者与消费者

    读者写者

    对一个公共数据进行写入和读取操作,和之前的生产者消费者模型很类似,我们梳理一下两者的区别。

    • 都是多个线程对同一块数据进行操作
    • 生产者与生产者之间互斥、消费者与消费者之间互斥、生产者与消费者之间互斥
    • 写者与写者之间互斥、读者与写者之间互斥、但读者与读者之间并发进行

    写优先是说当有读者进行读操作时,此时有写者申请写操作,只有等到所有正在读的进程结束后立即开始写进程

    定义PV操作

    /**
     * 封装的PV操作类
     * @count 信号量
     */
    class syn{        
        int count = 0;
        
        syn(){}
        syn(int a){count = a;}
    	//P操作
        public synchronized void Wait() {
            count--;
            if(count < 0) {        //block
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    	//V操作
        public synchronized void Signal() {
            count++;
            if(count <= 0) {    //wakeup
            	notify();
            }
        }
    }
    

    全局信号量

    全局信号量中用到了三个信号量w、rw、mutex,初始化都等于1。下面一一做解释。

    • 先从最简单的mutex说,mutex用来互斥访问count变量,对读者数目的加加减减。
    • 然后是rw,当第一个读进程进行读操作时候,会持有rw锁而不释放,在它读的过程中如果有写进程想要写数据,就无法在此时进行写操作,此时可能还会进来多个读进程,而只有当最后一个读进程执行完读操作的时候才会将rw锁释放。从而保证了如果在有一个或多个读者正在进行读操作时,写进程试图写数据,只能等到所有正在读的进程读完才行。
    • 最后是w锁,也是最复杂的一个,作用有二:
      • 保证了写者与写者之间的互斥,这个是很简单的
      • 保证了写优先的操作,是必要而不充分条件。如果此时有三个读进程正在进行读操作,而此时有一个写进程进入试图进行写操作,由于第一个读者进入时持有了rw锁,而导致写者在持有w锁后(读者进程虽然刚开始也会持有w锁,但都是很快又释放的,所以不影响写进程获取w锁资源)被wait在rw锁那块,其实执行的wait方法是rw.wait(),而它本身还是持有w锁的,也就是说之后如果还有读/写进程试图进行读操作时,就会在刚开始因为无法获取w锁资源而被wait,执行的wait语句是w.wait(),因为w锁被写进程持有,所以在写进程写完之前都不会释放,当最后一个读者读完后,执行notify方法,其实是对rw锁的释放rw.notify(),此时也只有那个等待的写者进程可以被唤醒,从而实现了写优先的操作。
    class Global{
        static syn w = new syn(1);			//让写进程与其他进程互斥
        static syn rw = new syn(1);			//读者和写者互斥访问共享文件
        static syn mutex = new syn(1);	//互斥访问count变量
        static int count = 0;						//给读者编号
    }
    

    写者进程

    /**
     * 写者进程
     */
    class Writer implements Runnable{
    	@Override
    	public void run() {
    		while(true) {
    			Global.w.Wait();		//两个左右,为了写者的互斥和写优先(持有w锁,让后面的读进程无法进入)
    			Global.rw.Wait();		//互斥访问共享文件,如果有读进程此时正在读,则会由于缺少rw锁而在此等待rw.wait()
    			/*写*/
    			System.out.println(Thread.currentThread().getName()+"我是作者,我来写了,现在有"+Global.count+"个读者还在读");
    			try {
    				Thread.sleep(new Random().nextInt(3000));		//随机休眠一段时间,模拟写的过程
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			System.out.println(Thread.currentThread().getName()+"我写完了");
    			Global.rw.Signal();		//释放共享文件
    			Global.w.Signal();		//恢复其他进程对共享文件的访问
    			try {
    				Thread.sleep(new Random().nextInt(3000));
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    

    读者进程

    /**
     * 读者进程
     */
    class Reader implements Runnable{
    	@Override
    	public void run() {
    		while(true) {
    			Global.w.Wait();		//为了写优先,当有写进程在排队时,写进程持有w锁,之后进入的读进程由于缺少w锁资源,会一直等待到写进程写完才能获取w锁
                Global.w.Signal();		//此时必须释放,不然就不能保证读进程之间的并发访问,因为不释放,这个进程就会一直持有w锁,其他读进程就无法进入
    			Global.mutex.Wait();	//互斥访问count变量
    			if(Global.count == 0) {		//进入的是第一个读者
    				Global.rw.Wait();		//占用rw这个锁,直到正在进行的所有读进程完成,才会释放,写进程才能开始写,保证读写的互斥
    			}	
    			Global.count++;		//读者数量加1
    			System.out.println("现在是读的时间,我是第"+Global.count+"号读者");
    			Global.mutex.Signal();
    			
    			/*读*/
    			try {
    				Thread.sleep(new Random().nextInt(3000));
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			
    			Global.mutex.Wait();	//互斥访问count变量
    			Global.count--;
    			System.out.println("我是第"+(Global.count+1)+"号读者,我读完了");
    			if(Global.count == 0) {		//最后一个读进程读完
    				Global.rw.Signal();		//允许写进程开始写
    			}
    			Global.mutex.Signal();	
    		}
    	}
    }
    

    实验过程遇到的问题

    1. 模型的整体梳理

    多个读者和多个写者同时共享一块数据区,采取写优先,读者与写者互斥、写者与写者互斥。读者读的时候可以有别的读者进来读,但是一个写者写的时候,不允许其他写者进入来写,也不允许读者进来读,写者进入的时候必须保证共享区没有其他进程。

    写进程

    在数据区写数据,用w锁使得写者和写者之间互斥,即一个写者正在写的时候,其他写者无法进入。由于读者进入时也需呀w锁,所以会由于未持有w锁的资源而被加入w锁的等待队列w.wait()

    写进程写的时候需要同时持有w和rw锁,这样当有读者正在读的时候来了一个写进程持有w锁后发现未有rw锁,进入rw的等待队列rw.wait(),而自己又持有了w锁,所以后面来的读者就会因为缺少w锁而进入w锁的等待队列进行等待,w.wait(),当之前的所有读进程读完后释放rw锁,这时只有处于rw锁等待队列的写进程能进入数据区写,这样就实现了写优先。

    读进程

    在数据区读数据,进入时需要持有w锁,然后立即释放即可。目的是如果有写进程正在写(或者正在排队)就会由于w锁被写进程持有而进入等待队列。同时第一个读者进入的时候需要拿走rw锁,目的是告诉外面其他进程有读进程正在里面读,而由于读进程之间是并发的,所以只需要在第一个读进程进入时持有rw锁即可。

    2. 等待队列问题,即写优先的实现(对去掉读者w信号量后出现一直是读者,几乎没有写者现象的解释)

    去掉读者的w锁后,写优先就无法实现。去掉后读者进入数据区不再需要持有w锁,这样如果此时有三个读者正在读,然后有一个写者请求进入写数据,由于缺少rw锁进入rw等待队列。这时又来了两个读者进程请求进入数据区读数据,由于不用和之前一样必须持有w锁,所以就会直接进入数据区开始读数据,这样再后面进来的写者都会进入w锁等待队列(w锁被上一个在rw等待队列的写者持有),所以之后将不会再出现写者,而读者不受影响,所以之后就只剩读者进程操作。

    3. 读者顺序123开始321结束现象的解释

    原因在于输出的count值是公有的,当你看到3号读者进入时,count已经等于3了,这样后面不管是那个进程结束,输出时count 都等于3,所以这时候count的值并不能代表是第几个读者,而是剩余读者的数目。

    当第一个读者进入后拿到mutex,执行count++,然后执行System.out.println("现在是读的时间,我是第"+Global.count+"号读者");这句输出语句,然后释放mutex,这时CPU切换到第二个读者,继续执行之前的步骤,当第三个读者输出完这句话时,这时候的count已经等于3了,所以当CPU不论切换到那个读进程输出System.out.println("我是第"+(Global.count+1)+"号读者,我读完了");这句话,都会从大往小输出,因为count值是公有的。

    3.1 调整

    设置一个per类,表示person,里面有一个count成员,每次count++后,在进程中创建一个per对象,用Global.count初始化,这样读者读完数据输出自己结束的时候输出这个线程对象的成员count。

    class per{
    	int count;
    	public per(int a) {
    		count = a;
    	}
    }
    
    
    class Reader implements Runnable{
    	@Override
    	public void run() {
    		while(true) {
    			Global.w.Wait();		//在无写请求时进入
    			Global.w.Signal();
    			Global.mutex.Wait();	//互斥访问count变量
    			if(Global.count == 0) {		//第一个读者
    				Global.rw.Wait();		//指示写进程在此时写
    			}	
    			Global.count++;		//读者数量加1
    			per per = new per(Global.count);			//用这个对象唯一地标识这个读者进程
    			System.out.println("现在是读的时间,我是第"+Global.count+"号读者");
    			Global.mutex.Signal();
    			
    			/*读*/
    			try {
    				Thread.sleep(new Random().nextInt(3000));
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			
    			Global.mutex.Wait();	//互斥访问count变量
    			Global.count--;
    			System.out.println("我是第"+per.count+"号读者,我读完了");		//通过对象的count成员就知道是第几个读者线程结束了
    			if(Global.count == 0) {		//最后一个读进程读完
    				Global.rw.Signal();		//允许写进程开始写
    			}
    			Global.mutex.Signal();	//释放互斥count锁
    		}
    	}
    }
    

    这时读者的输出就会是正常的无序状态(因为CPU调度是随机的)。

  • 相关阅读:
    怎么查看京东店铺的品牌ID
    PPT编辑的时候很卡,放映的时候不卡,咋回事?
    codevs 1702素数判定2
    codevs 2530大质数
    codevs 1488GangGang的烦恼
    codevs 2851 菜菜买气球
    hdu 5653 Bomber Man wants to bomb an Array
    poj 3661 Running
    poj 1651 Multiplication Puzzle
    hdu 2476 String Painter
  • 原文地址:https://www.cnblogs.com/vfdxvffd/p/13660545.html
Copyright © 2020-2023  润新知