PING-PONG机制_ping的工作原理[通俗易懂]

PING-PONG机制_ping的工作原理[通俗易懂]乒乓缓冲机制乒乓缓冲机制在很多场合都有应用价值,将其抽象成某种通用化类库,使代码得以复用

乒乓缓冲机制

一、认识Ping-pong工作模式
Ping-pong实质是一种数据缓冲的手段(是一种数据传输技术),能够同时利用两个数据缓冲区达到数据连续传输的目的,从而提高数据传输速率。由于单个缓冲区得到的数据在传输和处理中很容易被覆盖,而Ping-pong 缓冲区的方式能够总是保持一个缓冲区的数据被利用,另一个缓冲去用于存储数据。即两个相同的对象作为缓冲区交替地被读和被写。

 

二、Ping-pong模式&普通数据交换模式
 

1. 普通数据交换机制
项目中两个模块间交换数据时,第一个模块(上级)向另一个模块发送数据,接受模块(下级)不能马上处理完成并返回,这样上级必须等待下级处理完成才可以送新的数据,这样就会对性能产生很大的损失,影响了数据传输效率。

PING-PONG机制_ping的工作原理[通俗易懂]

2. ping-pong数据交换模式
pingpong机制是一种数据交换机制,我们可以不去等待接受模块(下级)处理结束,而是发送模块(上级)继续执行并将结果保存在ping路的缓存中,上级继续执行到一定时刻,下级模块处理完成将结果保存在pong路中),这样可以下级模块无需等待继续执行,上级也无需等待继续执行,转而将结果存储在ping路。这样便提高了处理效率。

PING-PONG机制_ping的工作原理[通俗易懂]
 

3.ping-pong详解

  乒乓缓冲机制在很多场合都有应用价值,将其抽象成某种通用化类库,使代码得以复用。那么首先就要抽象出此机制的抽象模型。
  
  乒乓缓冲应该有两个相同的对象作为缓冲区(对象类型可以是任意的),两者交替地被读和被写。在卷轴的例子中,向可见区域移动就是读操作,生成并绘制就是写操作。读写的过程在两个缓冲区之间交替进行:一开始两个缓冲内容均无效,不能被读;然后写0,完毕后0可读,再写1使1可读,同时可以读0,读完后0变成可写状态;1写毕后变成可读状态……由此可见对于每个缓冲区来说,可能的状态有四种并按如下顺序循环往复地转换:可读=>在读=>可写=>在写=>可读=>……
  一个BiBuf类负责维护两个缓冲对象(长度为2的Object类型数组,名为bufs)及记录其状态。状态常量这样设定:0=在读, 1=可读(写毕), 2=可写(读毕), 3=在写。 于是两个缓冲状态的表示用一个长度为2的byte型数组即可(当然完全可以放在一个byte变量的高4位和低4位,不过不够清晰易懂)。但仅有这个指示某块缓冲“可以做什么”的变量还不够,还要有个指示“应该做什么”的变量。因为对于单一的读或写操作来说,可操作的缓冲对象必须是交替轮换的,比如两个缓冲都写满之后,都是可读的状态,这时实际应读哪一个呢?所以还应该有两个变量bufToRead和bufToWrite,分别指示当前应该读和应该写的缓冲序号。
  缓冲对象数组被设成私有的以阻止外部对象直接对其操作。为了读写,BiBuf类将有一个openBuf(char request)方法用于按request指定的读写请求(‘w’为写,’r’为读)打开一个缓冲对象。方法会阻塞直至“应该”被操作的对象已经“可以”进行指定的操作,然后重新设定该块缓冲对象的状态变量。

4.ping-pong 机制代码实现
BiBuffer.java :

public class BiBuffer {

  //……

    public synchronized Object openBuf(char request) {

        //必须用synchronized关键字,以实现对状态变量rwstat读写的互斥
        if (request==’w’) {

          //若请求的是写操作
            try {

            while (rwstat[bufToWrite]!=READDONE) {

              //阻塞直至bufToWrite号缓冲可写
                wait();
            }
            }catch(InterruptedException inte){}
            rwstat[bufToWrite]=WRITING;//把状态置为在写
            return bufs[bufToWrite];//返回该块缓冲对象
        }
        else if (request==’r’) {

            try {

            while (rwstat[bufToRead]>=READDONE) {

              //阻塞直至bufToRead号缓冲可读
                wait();
            }
            }catch(InterruptedException inte){}
            rwstat[bufToRead]=READING;//把状态置为在读
            readingReaders++;//读者数加1(因为可以被不止一个对象读)
            return copydata(bufs[bufToRead]);//返回该块缓冲对象的拷贝
        }
        else return null;
        
    }
    
    //……
}
  需要解释一下copydata()方法,它的作用是返回一个缓冲对象的拷贝,因为“读”操作与“写”操作的本质区别是,它原则上是不能修改原对象的,所以要返回一个拷贝供读者对象去“读”。但其默认的做法是直接返回原缓冲对象本身,这是出于效率的考虑:
  public Object copydata(Object src) {

     //子类可重写之
     return src;
    }
当然,方法是留给子类去继承的,具体实现依赖于子类的设计。
  为什么要单独设计一个copydata方法而不是把缓冲对象声明为Clonable,直接调用其clone()方法呢?这仅仅是为了不必因为缓冲对象要实现Clonable接口,还要去写一个其原型的子类罢了(何况还应考虑到有的类可能被设置为final,不可继承的)。
  
  一旦一个缓冲对象被openBuf()打开后,其状态值就被标成相应的“正在进行”状态,这时其它的线程要去打开它的话也就只能阻塞直至其完成操作(除了正在读时另外的读者去打开它时,因为“读”是可以多个线程同时进行的),于是就可以放心大胆地对其进行读写。而读写操作完成后,应调用closeBuf(Object buf)方法设置buf所引用的缓冲的状态为“完成”。
  //……
  
    public synchronized boolean closeBuf(Object buf) {

      int bufid;
      if (bufs[0]==buf) bufid=0;
      else if (bufs[1]==buf) bufid=1;
      else return false;
      
        try {

        if (rwstat[bufid]==WRITING) {

            //若该块缓冲是被写的
            rwstat[bufid]=WRITTEN;//设状态为可读
            bufToWrite=1-bufToWrite;//当前应写的缓冲切换到另一个
            return true;
        }
        else if (rwstat[bufid]==READING) {

            //若该块缓冲是被读的
            if ((–readingReaders)==0) rwstat[bufid]=READDONE;//递减读者数量。若减到0则置状态为可写
            bufToRead=1-bufToRead;//当前应读的缓冲切换到另一个
            return true;
        }
        else return false;
        }finally {

            notifyAll();//唤醒所有因条件不满足而等待的线程
        }
        
    }
    
    //……
    
  两个缓冲对象的初始化操作通过方法initdata来完成。它既可以接默认设计直接在由构造方法参数传入的对象数组上进行初始化,也可以自己生成一个新的数组,具体如何做完全交给子类去重写,赋予这个设计以充分的灵活性。下面是该类的其余部分代码:
public class BiBuffer {

    private Object bufs[];
    private byte rwstat[];
    private int bufToRead, bufToWrite;
    private int readingReaders;
    
    public static final byte READING = 0;
    public static final byte WRITTEN = 1;
    public static final byte READDONE = 2;
    public static final byte WRITING = 3;
    
    public Object[] initdata(Object[] data) {

     //子类可重写之
     return data;
    }
    
    public Object copydata(Object src) {

     //子类可重写之
     return src;
    }
    
    public void reset() {

       rwstat[0]=READDONE; rwstat[1]=READDONE;
       readingReaders=0;
       bufToWrite=0; bufToRead=0; 
    }
    
    public BiBuffer(Object[] data) {

       rwstat=new byte[2];
       reset();
       bufs=initdata(data);
    }
    
  //……
  
}
    
  到此为止这个类应该算比较完善了。但还有一点不太令人满意:openBuf(),读写,closeBuf(),这样的操作步骤是要靠读写者自己去做的,有点麻烦,况且不能保证编写程序的人会不会忘记在用完后closeBuf。我希望在提供openBuf()和closeBuf()这两个接口之外,还有一种更方便的办法。下文就介绍这个增强型的设计。

 

转自: http://www.cnblogs.com/qinjunni/archive/2012/02/20/2359583.html

今天的文章PING-PONG机制_ping的工作原理[通俗易懂]分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/66986.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注