简体   繁体   English

面向批处理排水的非阻塞队列

[英]Non-Blocking Queue with batch oriented drainAll

We have a specialist, multi-producer (User) and single-consumer (Engine), queue. 我们有一个专家,多生产者(用户)和单消费者(引擎)队列。 The User threads runs more frequently and always adds individual elements to the queue. 用户线程运行频率更高,并且总是将单个元素添加到队列中。 The Engine thread operation runs less frequently and processes the stack elements in a batch. Engine线程操作的运行频率降低,并批量处理堆栈元素。 If the stack is empty, it'll park until the User thread has added an entry. 如果堆栈为空,它将停放,直到用户线程添加了一个条目。 This way a notify only needs to happen when the queue goes from empty to 1. 这样,仅当队列从空变为1时才需要发生通知。

In this implementation, instead of the Engine thread iterating and removing one item at a time, it removes them all - a drainAll, instead of drainTo. 在此实现中,不是将Engine线程一次迭代和删除一个项目,而是将它们全部删除了-rainyAll,而不是raintTo。 No other operations can mutate the stack - just the User thread add, and the engine thread drainAll. 没有其他操作可以使堆栈发生变化-仅添加User线程,并删除引擎线程rainAll。

Currently we do this via a synchronised linked list, we are wondering if there is a non-blocking way to do this. 当前,我们通过同步链表进行此操作,我们想知道是否存在非阻塞方法。 The drainTo operation on JDK classes will iterate the stack, we just want to take everything in the stack in one operation, without iterating - as each iteration hits volatile/cas related logic, so we'd ideally just like to hit that once, per drainAll. 在JDK类上的raintTo操作将迭代堆栈,我们只想在一个操作中进行堆栈中的所有内容,而无需进行迭代-每次迭代都遇到与volatile / cas相关的逻辑,因此理想情况下,我们希望每次迭代一次drainAll。 The the engine thread can iterate and operate on each individual element, without touching sync/volatile/cas operations. 引擎线程可以迭代并在每个单独的元素上运行,而无需进行同步/易失性/ cas操作。

The current implementation looks something like: 当前的实现类似于:

public class SynchronizedPropagationQueue implements PropagatioQueue {
    protected volatile PropagationEntry head;
    protected volatile PropagationEntry tail;

    protected synchronized void addEntry( PropagationEntry entry ) {
        if ( head == null ) {
            head = entry;
            notifyWaitOnRest();
        } else {
            tail.setNext( entry );
        }
        tail = entry;
    }

    @Override
    public synchronized PropagationEntry drainAll() {
        PropagationEntry currentHead = head;
        head = null;
        tail = null;
        return currentHead;
    }

    public synchronized void waitOnRest() {
        try {
            log.debug("Engine wait");
            wait();
        } catch (InterruptedException e) {
            // do nothing
        }
        log.debug("Engine resumed");
    }


    @Override
    public synchronized void notifyWaitOnRest() {
        notifyAll();
    }
}

asdf ASDF

Stacks have a very simple non-blocking implementation that supports a concurrent "pop all" operation easily, and can easily detect the empty->non-empty transition. 堆栈具有非常简单的非阻塞实现,可轻松支持并发的“全部弹出”操作,并且可以轻松检测到空->非空过渡。 You could have all your producers push items onto a stack and then have the engine empty the whole thing at once. 您可以让所有生产者将物品推入堆栈,然后让引擎立即清空整个物品。 It looks like this: 看起来像这样:

public class EngineQueue<T>
{
    private final AtomicReference<Node<T>> m_lastItem = new AtomicReference<>();

    public void add(T item)
    {
        Node<T> newNode = new Node<T>(item);
        do {
            newNode.m_next = m_lastItem.get();
        } while(!m_lastItem.compareAndSet(newNode.m_next, newNode)); 

        if (newNode.m_next == null)
        {
            // ... just went non-empty signal any waiting consumer
        }
    }

    public List<T> removeAll()
    {
        Node<T> stack = m_lastItem.getAndSet(null);
        // ... wait for non-empty if necessary 
        List<T> ret = new ArrayList<>();
        for (;stack != null; stack=stack.m_next)
        {
            ret.add(stack.m_data);
        }
        Collections.reverse(ret);
        return ret;
    }
    private static class Node<U>
    {
        Node<U> m_next;
        final U m_data;
        Node(U data)
        {
            super();
            m_data = data;
        }
    }
}

For signaling around the empty -> non-empty transition, you can use normal synchronization. 要在空->非空过渡周围发信号,可以使用普通同步。 This is not going to be expensive if you only do it when you detect an empty state... since you only get to the empty state when you're out of work to do. 如果仅在检测到空状态时才这样做,这将不会很昂贵...因为您只有在没有工作时才能进入空状态。

Currently we do this via a synchronised linked list, we are wondering if there is a non-blocking way to do this. 当前,我们通过同步链表进行此操作,我们想知道是否存在非阻塞方法。 The drainTo operation on JDK classes will iterate the stack, we just want to take everything in the stack in one operation, without iterating JDK类上的rainingTo操作将迭代堆栈,我们只想在一个操作中获取堆栈中的所有内容,而无需迭代

Maybe I don't understand but it seems like using a BlockingQueue.drainTo(...) method would be better than your implementation. 也许我听不懂,但是使用BlockingQueue.drainTo(...)方法似乎比您的实现更好。 For example the LinkedBlockingQueue.drainTo(...) method just has one lock around that method -- there's no iterating overhead that I see. 例如, LinkedBlockingQueue.drainTo(...)方法在该方法周围只有一个锁-我没有迭代开销。

If this is not an academic discussion then I'd doubt that your performance problems are with the queue itself and would concentrate your efforts in other areas. 如果这不是学术讨论,那么我会怀疑您的性能问题是否出在队列本身上,并且会将您的精力集中在其他领域。 If it is academic then @Matt's answer might be better although certainly there's a lot more code to be written to support the full Collection method list. 如果这是学术性的,那么@Matt的答案可能会更好,尽管肯定还有很多代码需要编写以支持完整的Collection方法列表。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM