多線程的PipeLine實現實例

多生產者多消費者的Blocking Queue

/**
 * A blocking queue which wraps {@code ArrayBlockingQueue}, and with the
 * following features. 
 * <li>Use null object as end of queue.
 * <li>Supports muti-producers for the queue. Only if all the producers put a position-pill to
 * the queue, the queue will reach end of life.
 * 
 * @param <T>
 *            element type
 */
public class Queue<T> {
    
    private ArrayBlockingQueue<Element<T>> queue = null;
    
    private AtomicInteger lifeValue ;   
    
    private static int DEFAULT_LIFE_VALUE = 1;
    
    /**
     * 
     * @param capacity The queue size
     * @param lifeValue It is set as the number of producer of the queue as a rule
     */
    public Queue(int capacity, int lifeValue) {
        this.queue = new ArrayBlockingQueue<Element<T>>(capacity);
        this.lifeValue = new AtomicInteger(lifeValue);
    }
       
    public Queue(int capacity) {
        this( capacity, DEFAULT_LIFE_VALUE );
    }
    
    /**
     * Put an position-pill to the queue.  
     * @throws InterruptedException
     */
    @SuppressWarnings("unchecked")
    public void putEnd() throws InterruptedException{
        int life = this.lifeValue.decrementAndGet();
        if(life <= 0 ){
            this.queue.put(Element.NULL);
        }
    }
    
    /**   
     * Put an element to the queue
     * @param element
     * @throws InterruptedException
     */
    public void put(T element ) throws InterruptedException{
        try {
            queue.put(new Element<T>(element));
        } catch (InterruptedException e) {
            this.queue.clear();
            throw e;
        }
    }
    
    /**
     * Take an element from the head of queue. 
     * If queue is empty, the operation will be blocked.
     * 
     * @return a non-nullable object if queue if not empty, or null if the queue is ended
     * @throws InterruptedException 
     */
    public T take() throws InterruptedException{
        Element<T> element = this.queue.take();
        if( Element.NULL == element){
            this.queue.put(Element.NULL);
        }
        return element.getObject();        
    } 
    
    /**
     * Take elements in batch from the queue. The operation will be blocked for a non-ended queue.
     * The fetched list size should be equal to count unless the queue is ended
     * @param count the desired element size to fetch
     * @return a List which contains the batch fetched element, or null if the queue is ended and empty
     * @throws InterruptedException
     */
    public List<T> batchTake(int count) throws InterruptedException{        
        List<T> result = new ArrayList<T>(count);
        int size = 0;
        while(size < count){
            T object = this.take();
            if(object == null){
                break;
            }else{                     
                result.add(object);
                size ++;
            }
        }
        if(result.isEmpty()){
            return null;
        }
        return result;
    }     
    
    /**
     * Wrapper of object, which supports null 
     *
     * @param <T>
     */
    private static class Element<T> {
        @SuppressWarnings({"unchecked","rawtypes"})
        static Element NULL = new Element(null);
        private T object;

        Element(T o) {
            this.object = o;
        }

        T getObject() {
            return this.object;
        }
    }
}


PipeLine調度框架

/**
 * 
 * An executor that uses multiple threads to run a job which involve muti-steps pipeline like source > intermediate steps > output.
 * 
 * <li> Steps can be executed concurrently, and they communicates with each other by {@code Queue}
 * <li> A step can be executed with muti-thread concurrently, and the thread number can be configured
 * 
 * @author yuliang
 *
 * @param <S> source type
 * @param <O> output type
 */
@SuppressWarnings("rawtypes")
public class ParallelPipeLineExecutor<S, O> {

    protected static final Logger LOGGER = Logger.getLogger(FourStepPiplineExecutor.class);
    
    private String pipeLineName;

    private ISource<S> source;

    private Queue<S> sourceQueue;

    private List<Queue> queues = new LinkedList<Queue>();

    private List<PipeLineStep> processorSteps = new LinkedList<PipeLineStep>();
        
    public ParallelPipeLineExecutor() {
    }
    
    public void setName(String pipeLineName) {
        this.pipeLineName = pipeLineName;
    }

    public void setSource(ISource<S> source, int sourceQueueSize) {
        this.source = source;
        this.sourceQueue = new Queue<S>(sourceQueueSize);
    }
    
    private String generateStepProcessorName(String stepName, int stepSequence){
        return this.pipeLineName+"."+stepName+"#"+stepSequence;
    }

    private <T1, T2> void addNextStep(String name, ProcessorProvider<T1, T2> provider, int threadNum, int outQueueSize) {
        String stepName = (name == null) ? "Step" + (processorSteps.size()+1) : name;
        PipeLineStep step = new PipeLineStep(stepName, provider, threadNum);
        this.processorSteps.add(step);
        if (outQueueSize > 0) {
            Queue<T1> outQueue = new Queue<T1>(outQueueSize, threadNum);
            queues.add(outQueue);
        }
    }

    /**
     * Add next step for the pipeline
     * 
     * @param provider
     *            a {@code Processor} provider
     * @param threadNum
     *            the thread number that run the step
     * @param outQueueSize
     *            the size of queue where the step output to
     */
    public <T1, T2> void addNextStep(ProcessorProvider<T1, T2> provider, int threadNum, int outQueueSize) {
        Preconditions.checkArgument(threadNum > 0, "threadNum: " + threadNum + "should be positive");
        Preconditions.checkArgument(outQueueSize >= threadNum, "outQueueSize: " + outQueueSize
                + "should be greater than threadNum:" + threadNum);
        if (provider instanceof IdentityProcessor.Provider) {
            LOGGER.info("Skip a step because it use Indentity.Provider");
        } else {            
            this.addNextStep(null, provider, threadNum, outQueueSize);
        }
    }

    /**
     * Add sink step for the pipeline
     * 
     * @param provider
     *            provider a {@code Processor} provider
     * @param threadNum
     *            the thread number that run the step
     */
    public void addSinks(ProcessorProvider<O, Void> provider, int threadNum) {
        Preconditions.checkArgument(threadNum > 0, "threadNum: " + threadNum + "should be positive");
        this.addNextStep("Output", provider, threadNum, 0);
    }

    /**
     * Launch the execution of the pipeline and wait until it is finished or exception occurs
     */
    public void launchAndWait() {
        int workersCount = 0;
        final ExecutorService executorService = Executors.newCachedThreadPool();
        ExecutorCompletionService<Worker> workerCompletionService = new ExecutorCompletionService<Worker>(
                executorService);
        try {
            //submit source worker
            Worker sourceWorker = new SourceWorker<S>(this.source, this.sourceQueue);            
            sourceWorker.setName(this.generateStepProcessorName("sourceWorker", workersCount));
            workerCompletionService.submit(sourceWorker);
            workersCount++;

            //submit processor workers including sink workers
            Queue inQueue = sourceQueue;
            Iterator<Queue> queuesIter = queues.iterator();
            for (PipeLineStep step : processorSteps) {
                final Queue outQueue = queuesIter.hasNext() ? queuesIter.next() : null;
                int stepSequence = 0;
                for (IProcessor processor : step.generateProcessors()) {
                    stepSequence ++; 
                    ProcessWorker worker = new ProcessWorker(inQueue, processor, outQueue);
                    worker.setName(this.generateStepProcessorName(step.getName(), stepSequence));
                    workerCompletionService.submit(worker);
                    workersCount++;
                }
                inQueue = outQueue;
            } 
            
            //wait for completion or exception
            while (workersCount > 0) {
                Worker w = workerCompletionService.take().get();
                if (w.getException() != null) {
                    LOGGER.error(w + " finished with exception");
                    throw w.getException();
                } else {
                    LOGGER.info(workersCount + " finished ");
                }
                workersCount--;
                LOGGER.info(workersCount + " threads still running...");
            }
        } catch (Exception e) {
            //Just throw exceptions so that job failure cause be be directly shown in DJS/HAM 
            throw new RuntimeException(e);
        } finally {
            executorService.shutdown();
        }
    }
    
    /**
     * A process step in the pipe line
     * 
     */
    private static class PipeLineStep {

        private String name;
        private ProcessorProvider processorProvider;
        private int concurrentThreadNum;

        public PipeLineStep(String name, ProcessorProvider provider, int threadNum) {
            this.name = name;
            this.processorProvider = provider;
            this.concurrentThreadNum = threadNum;
        }

        public String getName() {
            return name;
        }

        public List<IProcessor> generateProcessors() throws Exception {
            List<IProcessor> processors = new ArrayList<IProcessor>();
            for (int i = 0; i < concurrentThreadNum; i++) {
                processors.add(processorProvider.get());
            }
            return processors;
        }
    }

}

Worker類

/**
 * A {@code Worker} which can pull inputs from {@code inQueue}, process inputs 
 * and output the results to {@code outQueue} continuously
 * @author yuliang
 * 
 * @param <I> process input type
 * @param <O> process output type
 */
public class ProcessWorker<I, O> extends Worker {

    private Queue<I> inQueue;

    private IProcessor<I, O> processor;

    private Queue<O> outQueue;

    private int batchSize = 1;

    public ProcessWorker(Queue<I> inQueue, IProcessor<I, O> processor, Queue<O> outQueue) {
        super();
        Preconditions.checkArgument(batchSize > 0, "batchSize should be greater than 0");
        this.inQueue = inQueue;
        this.processor = processor;
        this.outQueue = outQueue;
        this.batchSize = processor.getBatchSize();
    }

    @Override
    public void work() throws Exception {
        try {
            if (this.batchSize > 1 && this.processor.isBatchSupported()) {
                this.processByBatch();
            } else {
                this.processOneByOne();
            }
        } finally {
            if (outQueue != null) {
                outQueue.putEnd();
            }
        }
    }

    /**
     * Fetch inputs from {@code inQueue}, and process inputs one by one and put
     * results to {@code outQueue}
     */
    private void processOneByOne() throws Exception {
        while (true) {
            I in = inQueue.take();
            if (in == null) {
                break;
            }
            O o = processor.process(in);
            if (o != null && outQueue != null) {
                outQueue.put(o);
            }
        }
    }

    /**
     * Fetch inputs from {@code inQueue}, and process inputs in batch and put
     * results to {@code outQueue}
     */
    private void processByBatch() throws Exception {
        while (true) {
            List<I> in = inQueue.batchTake(this.batchSize);
            if (in == null) {
                break;
            }
            List<O> outs = processor.batchProcess(in);
            if (outs != null && outQueue != null) {
                for (O o : outs) {
                    if (o != null) {
                        outQueue.put(o);
                    }
                }
            }
        }
    }
}
/**
 * 
 * A source worker is a {@code Worker} which produce source elements 
 * @author yuliang
 *
 * @param <S> source element type
 */
public class SourceWorker <S> extends Worker{

    private Queue<S> outQueue; 
    
    private ISource<S> source;
            
    public SourceWorker(ISource<S> source, Queue<S> sourceQueue) {
        super();
        this.source = source;
        this.outQueue = sourceQueue;    
    }    

    @Override
    public void work() throws Exception {
        try{
            while(true){                
                S o = source.nextSource();
                if(o == null){
                    break;
                }
                this.outQueue.put(o);
            }            
        }finally{
            this.outQueue.putEnd();           
        }
    }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章