38

I would like if you could explain to me in a simple way how does the disruptor patter work. This concept has been elusive to me as of know.

Perhaps with your help I could comprehend it.

Tulains Córdova
  • 39,201
  • 12
  • 97
  • 154
chrisapotek
  • 642
  • 1
  • 5
  • 8

3 Answers3

36

The Fowler Article providers a good primer, and this explanation:

At a crude level you can think of a Disruptor as a multicast graph of queues where producers put objects on it that are sent to all the consumers for parallel consumption through separate downstream queues. When you look inside you see that this network of queues is really a single data structure - a ring buffer.

Each producer and consumer has a sequence counter to indicate which slot in the buffer it's currently working on. Each producer/consumer writes its own sequence counter but can read the others' sequence counters. This way the producer can read the consumers' counters to ensure the slot it wants to write in is available without any locks on the counters. Similarly a consumer can ensure it only processes messages once another consumer is done with it by watching the counters.

enter image description here

A more conventional approach might use a Producer Queue and a Consumer Queue, each using locks as concurrency mechanisms. In practice, what happens with producer and consumer queues is that the queues are either completely empty or completely full most of the time, which causes lock contention and wasted clock cycles. The disruptor alleviates this, in part, by having all of the producers and consumers use the same queue mechanism, coordinating with each other by watching the sequence counters rather than using locking mechanisms.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
9

From this article about CoralQueue:

The disruptor pattern is a batching queue backed up by a circular array (i.e. the ring buffer) filled with pre-allocated transfer objects which uses memory-barriers to synchronize producers and consumers through sequences.

So producers and consumers do not step on each other inside the circular array by checking their corresponding sequences. And to communicate their sequences back and forth to each other they use memory-barriers instead of locks. That's the fastest lock-free way that they can communicate.

Fortunately you don't need to get down to the internal details of the disruptor pattern to use it. Besides the LMAX implementation there is CoralQueue developed by Coral Blocks, with which I am affiliated. Some people find it easier to understand a concept by reading code, so below is a simple example of a single producer sending messages to a single consumer. You can also check this question for a demultiplexer (one producer to many consumers) example.

package com.coralblocks.coralqueue.sample.queue;

import com.coralblocks.coralqueue.AtomicQueue;
import com.coralblocks.coralqueue.Queue;
import com.coralblocks.coralqueue.util.Builder;

public class Basics {

    public static void main(String[] args) {

        final Queue<StringBuilder> queue = new AtomicQueue<StringBuilder>(1024, new Builder<StringBuilder>() {
            @Override
            public StringBuilder newInstance() {
                return new StringBuilder(1024);
            }
        });

        Thread producer = new Thread(new Runnable() {

            private final StringBuilder getStringBuilder() {
                StringBuilder sb;
                while((sb = queue.nextToDispatch()) == null) {
                    // queue can be full if the size of the queue
                    // is small and/or the consumer is too slow

                    // busy spin (you can also use a wait strategy instead)
                }
                return sb;
            }

            @Override
            public void run() {

                StringBuilder sb;

                while(true) { // the main loop of the thread

                    // (...) do whatever you have to do here...

                    // and whenever you want to send a message to
                    // the other thread you can just do:
                    sb = getStringBuilder();
                    sb.setLength(0);
                    sb.append("Hello!");
                    queue.flush();

                    // you can also send in batches to increase throughput:
                    sb = getStringBuilder();
                    sb.setLength(0);
                    sb.append("Hi!");

                    sb = getStringBuilder();
                    sb.setLength(0);
                    sb.append("Hi again!");

                    queue.flush(); // dispatch the two messages above...
                }
            }
        }, "Producer");

        Thread consumer = new Thread(new Runnable() {

            @Override
            public void run() {

                while (true) { // the main loop of the thread

                    // (...) do whatever you have to do here...

                    // and whenever you want to check if the producer
                    // has sent a message you just do:

                    long avail;
                    while((avail = queue.availableToPoll()) == 0) {
                        // queue can be empty!
                        // busy spin (you can also use a wait strategy instead)
                    }

                    for(int i = 0; i < avail; i++) {
                        StringBuilder sb = queue.poll();
                        // (...) do whatever you want to do with the data
                        // just don't call toString() to create garbage...
                        // copy byte-by-byte instead...
                    }
                    queue.donePolling();
                }
            }
        }, "Consumer");

        consumer.start();
        producer.start();
    }
}

Disclaimer: I am one of the developers of CoralQueue.

rdalmeida
  • 250
  • 1
  • 5
1

Disruptor pattern is the circular buffer queue that based on the following concepts:

  1. Prevent false sharing using contended memory. Put the events into separated cache lines.
  2. Use memory barrier to propagate write signal to producers and consumers.
  3. Array backed to fast access to the continuous memory cells (based on cpu cache)
  4. Early initialization to prevent high GC time and decrease latency of event adding.
  5. Use busy spin loop and CAS instruction instead of mutex and semaphore to handle read and write contentions.