zoukankan      html  css  js  c++  java
  • 操作系统实验——读者写者模型(写优先)

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

    个人博客主页
    参考资料:
    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调度是随机的)。

  • 相关阅读:
    pandas 流式导出excel
    django serializer 定制error_message
    selenium etree xpath使用总结
    Python之路--Python基础
    初见Flask
    Git
    MySQL补充——索引,流程控制,数据备份,python操作mysql,SQLAlchemy
    Python之路--Django--Ajax、同源策略、Jsonp、CORS
    Python之路--Django--form组件与model form组件
    Python之路--Django--中间件
  • 原文地址:https://www.cnblogs.com/vfdxvffd/p/13660545.html
Copyright © 2011-2022 走看看