Alguém pode explicar em termos simples qual é o padrão de disruptor?

Respostas:

33

O Artigo Fowler fornece uma boa cartilha, e esta explicação:

Em um nível bruto, você pode pensar em um Disruptor como um gráfico multicast de filas, onde os produtores colocam nele objetos que são enviados a todos os consumidores para consumo paralelo por meio de filas posteriores separadas. Quando você olha para dentro, vê que essa rede de filas é realmente uma única estrutura de dados - um buffer de anel.

Cada produtor e consumidor tem um contador de sequência para indicar em qual slot do buffer está trabalhando. Cada produtor / consumidor grava seu próprio contador de sequência, mas pode ler os contadores de sequência dos outros. Dessa forma, o produtor pode ler os contadores dos consumidores para garantir que o slot em que deseja escrever esteja disponível sem bloqueios nos contadores. Da mesma forma, um consumidor pode garantir que ele processe apenas mensagens quando outro consumidor terminar observando os contadores.

insira a descrição da imagem aqui

Uma abordagem mais convencional pode usar uma Fila do Produtor e uma Fila do Consumidor, cada uma usando bloqueios como mecanismos de simultaneidade. Na prática, o que acontece com as filas de produtores e consumidores é que as filas ficam completamente vazias ou completamente cheias na maioria das vezes, o que causa contenção de bloqueio e ciclos de relógio desperdiçados. O disruptor alivia isso, em parte, fazendo com que todos os produtores e consumidores usem o mesmo mecanismo de fila, coordenando entre si observando os contadores de sequência em vez de usar mecanismos de bloqueio.

Robert Harvey
fonte
9

Deste artigo sobre CoralQueue :

O padrão do disruptor é uma fila de lotes com backup de uma matriz circular (ou seja, o buffer de anel) preenchida com objetos de transferência pré-alocados, que utiliza barreiras de memória para sincronizar produtores e consumidores por meio de seqüências.

Portanto, produtores e consumidores não se põem dentro da matriz circular verificando suas seqüências correspondentes . E para comunicar suas seqüências um para o outro, eles usam barreiras de memória em vez de bloqueios. Essa é a maneira mais rápida e segura de se comunicar.

Felizmente, você não precisa se concentrar nos detalhes internos do padrão de disruptor para usá-lo. Além da implementação do LMAX, existe o CoralQueue, desenvolvido pela Coral Blocks, ao qual sou afiliado. Algumas pessoas acham mais fácil entender um conceito lendo o código; portanto, abaixo está um exemplo simples de um único produtor enviando mensagens para um único consumidor. Você também pode verificar esta pergunta para obter um exemplo de desmultiplexador (um produtor para muitos consumidores).

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: Eu sou um dos desenvolvedores do CoralQueue.

rdalmeida
fonte
1
Seria bom indicar sua afiliação com o software que você descreve.
Deer Hunter