实践中的并发循环缓冲区错误?

Posted

技术标签:

【中文标题】实践中的并发循环缓冲区错误?【英文标题】:Concurrency In Practice Circular Buffer Error? 【发布时间】:2020-04-21 01:25:52 【问题描述】:

最后阅读了优秀的Concurrency In Practice一书,我发现了BaseBoundedBuffer的清单14.2。照原样, put 和 take 方法将允许 count 超过缓冲区容量或低于 0。我知道该类是抽象的,但似乎很奇怪,这是默认行为。是否有一些充分的理由可以解释不允许计数超出容量或低于 0 的逻辑?也许像,

if(count != buf.length)
     ++count;
@ThreadSafe
public abstract class BaseBoundedBuffer<V> 
     @GuardedBy("this") private final V[] buf;
     @GuardedBy("this") private final int tail;
     @GuardedBy("this") private final int head;
     @GuardedBy("this") private final int count;

     protected BaseBoundedBuffer (int capacity) 
          this.buf = (V[]) new Object[capacity];
     

     protected synchronized final void doPut(V v) 
          buf[tail] = v;
          if (++tail == buf.length)
               tail = 0;
          ++count;
     

      protected synchronized final V doTake() 
          V v = buf[head];
          buf[head] = null;
          if (++head == buf.length)
               head = 0;
          --count;
          return v;
     

     public synchronized final boolean isFull() 
          return count == buf.length;
     

     public synchronized final boolean isEmpty() 
          return count == 0;
     

【问题讨论】:

【参考方案1】:

似乎给出了书中的示例子类,它的目的是让子类有责任在放入之前检查isFull,在接受之前检查isEmpty。有了这样的实现,再次检查是浪费时间。

@ThreadSafe
public class GrumpyBoundedBuffer<V> extends BaseBoundedBuffer<V> 
    public GrumpyBoundedBuffer(int size)  super(size); 

    public synchronized void put(V v) throws BufferFullException 
        if (isFull())
            throw new BufferFullException();
        doPut(v);
    

    public synchronized V take() throws BufferEmptyException 
        if (isEmpty())
            throw new BufferEmptyException();
        return doTake();
    

在现实世界中,一个适当的 JavaDoc 解释如何使用这些方法对于避免您发现的两个潜在错误至关重要。

不言而喻,书中的内容并不意味着它是正确的、最佳的,甚至是好的。您对实施持怀疑态度是对的。

【讨论】:

完全理解这不是复制和粘贴代码,但更令人惊讶的是,没有某种文档表明存在潜在的错误,并且实施者有一个很大的假设,特别是考虑到过去关于文档需求的章节。【参考方案2】:

我们不应该让count 越界,但是这个例子假设检查这个条件被传播给调用者。我们不能只抛出异常,因为在多线程程序中,可能会以非异常方式预期和处理此类行为(例如,仅等待条件满足)。我们也不能只说if(count != buf.length) ++count;,因为这将是处理逻辑的一部分,并且可能与调用者或子类中实现的逻辑发生冲突。

这个例子是更大图景的一部分——14.1.1. Example: propagating precondition failure to callers 章节描述了一种由子类处理异常情况的方法。本章描述了实现此类功能的两种“痛苦”方法(抛出异常或sleeping 线程),然后提供了一种更健壮的方法 - 使用条件队列(参见第 14.1.3 章)。

我想强调的是,您提到的代码示例不是复制粘贴的实现,它只是直截了当的方法。

【讨论】:

当然,我理解这不是要使用的代码,并且考虑到作者试图强调其他内容的章节的重点,但从教学的角度来看,我更惊讶的是会有一个大型的、未记录的假设,即必须以某种方式实现该类才能使其正常工作。 我认为基类本身就是为了不在不同的示例中重复代码。它不应该被复制或扩展到本章示例代码之外的任何地方——所以本章文本中的解释对我来说似乎是足够的文档。

以上是关于实践中的并发循环缓冲区错误?的主要内容,如果未能解决你的问题,请参考以下文章

OpenAL 中的循环缓冲区

如何使用连接缓冲区(块嵌套循环)错误修复MySql的LEFT JOIN?

计算机系统漫游

C++ 中的高效循环缓冲区,将传递给 C 风格的数组函数参数

循环遍历存储在 C 中缓冲区中的数据

将包含循环缓冲区的类添加到 Vector