Multithreaded execution where order of finished Work Items is preserved

后端 未结 9 1187
无人共我
无人共我 2021-02-01 08:28

I have a flow of units of work, lets call them \"Work Items\" that are processed sequentially (for now). I\'d like to speed up processing by doing the work multithreaded.

<
相关标签:
9条回答
  • 2021-02-01 09:08

    You could launch a DoTask thread for every WorkItem. This thread processes the work. When the work is done, you try to post the item, synchronized on the controlling object, in which you check if it's the right ID and wait if not.

    The post implementation can be something like:

    synchronized(controllingObject) {
    try {
    while(workItem.id != nextId) controllingObject.wait();
    } catch (Exception e) {}
    //Post the workItem
    nextId++;
    object.notifyAll();
    }
    
    0 讨论(0)
  • 2021-02-01 09:11

    Just ID each of the objects for processing, create a proxy which would accept done work and allow to return it only when the ID pushed was sequential. A sample code below. Note how simple it is, utilizing an unsynchronized auto-sorting collection and just 2 simple methods as API.

    public class SequentialPushingProxy {
    
        static class OrderedJob implements Comparable<OrderedJob>{
            static AtomicInteger idSource = new AtomicInteger();
            int id;
    
            public OrderedJob() {
                id = idSource.incrementAndGet();
            }
    
            public int getId() {
                return id;
            }
    
            @Override
            public int compareTo(OrderedJob o) {
                return Integer.compare(id, o.getId());
            }
        }
    
        int lastId = OrderedJob.idSource.get();
    
        public Queue<OrderedJob> queue;
    
        public SequentialPushingProxy() {
            queue = new PriorityQueue<OrderedJob>();
        }
    
        public synchronized void pushResult(OrderedJob job) {
            queue.add(job);
        }
    
        List<OrderedJob> jobsToReturn = new ArrayList<OrderedJob>();
        public synchronized List<OrderedJob> getFinishedJobs() {
            while (queue.peek() != null) {
                // only one consumer at a time, will be safe
                if (queue.peek().getId() == lastId+1) {
                    jobsToReturn.add(queue.poll());
                    lastId++;
                } else {
                    break;
                }
            }
            if (jobsToReturn.size() != 0) {
                List<OrderedJob> toRet = jobsToReturn;
                jobsToReturn = new ArrayList<OrderedJob>();
                return toRet;
            }
            return Collections.emptyList();
        }
    
        public static void main(String[] args) {
            final SequentialPushingProxy proxy = new SequentialPushingProxy();
    
            int numProducerThreads = 5;
    
            for (int i=0; i<numProducerThreads; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        while(true) {
                            proxy.pushResult(new OrderedJob());
                        }
                    }
                }).start();
            }
    
    
            int numConsumerThreads = 1;
    
            for (int i=0; i<numConsumerThreads; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        while(true) {
                            List<OrderedJob> ret = proxy.getFinishedJobs();
                            System.out.println("got "+ret.size()+" finished jobs");
                            try {
                                Thread.sleep(200);
                            } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                        }
                    }
                }).start();
            }
    
    
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            System.exit(0);
        }
    
    }
    

    This code could be easily improved to

    • allow pushing more than one job result at once, to reduce the synchronization costs
    • introduce a limit to returned collection to get done jobs in smaller chunks
    • extract an interface for those 2 public methods and switch implementations to perform tests
    0 讨论(0)
  • 2021-02-01 09:15

    Pump all your Futures through a BlockingQueue. Here's all the code you need:

    public class SequentialProcessor implements Consumer<Task> {
        private final ExecutorService executor = Executors.newCachedThreadPool();
        private final BlockingDeque<Future<Result>> queue = new LinkedBlockingDeque<>();
    
        public SequentialProcessor(Consumer<Result> listener) {
            new Thread(() -> {
                while (true) {
                    try {
                        listener.accept(queue.take().get());
                    } catch (InterruptedException | ExecutionException e) {
                        // handle the exception however you want, perhaps just logging it
                    }
                }
            }).start();
        }
    
        public void accept(Task task) {
            queue.add(executor.submit(callableFromTask(task)));
        }
    
        private Callable<Result> callableFromTask(Task task) {
            return <how to create a Result from a Task>; // implement this however
        }
    }
    

    Then to use, create a SequentialProcessor (once):

    SequentialProcessor processor = new SequentialProcessor(whatToDoWithResults);
    

    and pump tasks to it:

    Stream<Task> tasks; // given this
    
    tasks.forEach(processor); // simply this
    

    I created the callableFromTask() method for illustration, but you can dispense with it if getting a Result from a Task is simple by using a lambda instead or method reference instead.

    For example, if Task had a getResult() method, do this:

    queue.add(executor.submit(task::getResult));
    

    or if you need an expression (lambda):

    queue.add(executor.submit(() -> task.getValue() + "foo")); // or whatever
    
    0 讨论(0)
提交回复
热议问题