山一程--软件开发--并行计算

目的: 提升数据及任务处理性能.


大纲:

1.参考

2.核心


 

1.参考

2.核心


 

1.2023-07-30 13:55:59

Completion Service<V> 用于从已完成任务(消费者)结果的消费中解耦新的异步任务(生产者)的生产。

生产者调用 submit() 方法提交一个任务执行(通过一个工作线程)

有个方法接收 callable 参数,  另一个接收 runnable 和 一个任务完成的返回值.

都返回一个 Future<V> 的实例. 代表该任务的未完成状态.

之后可以调用 poll () 方法来轮询这个任务的完成状态.

或者调用 take() 阻塞式方法.


消费者通过调用 take() 方法获取一个已完成的任务.该方法在完成前一直阻塞.之后会返回一个 Future<V> 对象来表示这一完成的任务,

调用 Future<V>的 get() 方法即可获取结果.


ExecutorCompletionService<V> 提供 executor 用于支持任务执行,它保证当提交过的任务完成时,这些任务会被放到一个可被 take()方法访问的队列中.

public class CSDemo {

    public static void main(String... arg) throws Exception {

        ExecutorService executors = Executors.newFixedThreadPool(2);

        CompletionService<BigDecimal> cs =
                new ExecutorCompletionService<>(executors);

        cs.submit(new CalculateE(17));
        cs.submit(new CalculateE(170));

        Future<BigDecimal> resultTake1 = cs.take();
        System.out.println((resultTake1.get()));
        System.out.println(("--separator--"));

        Future<BigDecimal> resultTake2 = cs.take();
        resultTake2.get();
        System.out.println(resultTake2.get());
        executors.shutdown();
    }
}
CompletionService
public class CalculateE implements Callable<BigDecimal> {

    final int lastIter;

    public CalculateE(int lastIter){
        this.lastIter = lastIter;
    }

    private BigDecimal factorial(BigDecimal n){
        if(n.equals(BigDecimal.ZERO)){
            return BigDecimal.ONE;
        }else{
            return n.multiply(factorial(n.subtract(BigDecimal.ONE)));
        }
    }

    @Override
    public BigDecimal call() throws Exception {
        MathContext mathContext = new MathContext(100, RoundingMode.HALF_UP);
        BigDecimal result = BigDecimal.ZERO;
        for(int i =0; i<=lastIter;i++){
            BigDecimal factorial = factorial(new BigDecimal(i));
            BigDecimal res =BigDecimal.ONE.divide(factorial,mathContext);
            result   = result.add(res);
        }
        return result;
    }
}
View Code

executor service 和 completion service 的区别:

1. executor service 写完提交任务的代码之后,需要写代码去高效地获取任务的结果.

  executor service 为任务提供了一个输入队列 和 多条工作线程,

2. completion service 更加自动化。

   completion service 则为任务提供了输入队列,工作线程 以及一个输出队列来存储任务的结果.


2. Fork/Join framework

由特定的 ExecutorService 和线程池构成. 任务分而治之, 从线程池中被 fork (被不同的线程执行) 出来,

会一直等待 join (即所有子任务都完成) 之前会一直等待.

任务窃取.  最小化线程的争用和开销.

线程池中的每个线程都有自己的双端队列并且会将新任务放到这个队列中. 

从队列的头部读取任务. 如果队列是空的,工作线程会尝试从另一个队列的末尾获取一个任务.

窃取操作不会频繁,工作线程会 LIFO 后进先出. 工作规模会分割变小。只会涉及少量同步操作.

1. ForkJoinPool 是 ExecutorService 实现. 实例提供了非ForkJoinTask 客户端的入口,提供了管理和监听操作.

2. ForkJoinTask 抽象基类. 专门运行在 ForkJoinPool 上下文中的任务, 实例类似线程的实体,但是轻量很多.

 实现类: RecursiveAction ,递归,无结果的 ForkJoinTask

                RecursiveTask 递归,有结果的 ForkJoinTask

                CountedCompleter : 带有完成动作(完成 fork/join任务)ForkJoinTask, 在被触发且没有其他滞留的动作时这个任务会被执行.

3. ForkJoinWorkerThread 被 ForkJoinPool 管理的一个线程.


public class MatMultRecursiveAction extends RecursiveAction {

    private final Matrix a,b,c;
    private final int row;

    public MatMultRecursiveAction(Matrix a, Matrix b,Matrix c){
        this(a,b,c,-1);
    }

    public MatMultRecursiveAction(Matrix a, Matrix b, Matrix c, int row){
        if(a.getCols() != b.getRows()){
            throw new IllegalArgumentException("rows/cols mismatch");
        }
        this.a=a;
        this.b=b;
        this.c=c;
        this.row = row;
    }

    @Override
    protected void compute() {
        if(row == -1){
            List<MatMultRecursiveAction> tasks = new ArrayList<>();
            for(int row=0; row < a.getRows();row++){
                tasks.add(new MatMultRecursiveAction(a,b,c,row));
            }
            invokeAll(tasks);
        }else{
            multiplyRowByColumn(a,b,c,row);
        }
    }

    public static void multiplyRowByColumn(Matrix a, Matrix b, Matrix c, int row){
        for(int j = 0; j < b.getCols(); j++){
            for(int k = 0; k < a.getCols(); k++){
                c.setValue(row,j,c.getValue(row,j) +
                        a.getValue(row,k) * b.getValue(k,j));
            }
        }
    }

}
RecursiveTask
public class MatMult {

    public static void main(String[] args){

        Matrix a = new Matrix(1,3); // | 1 2 3 |
        a.setValue(0,0,1);
        a.setValue(0,1,2);
        a.setValue(0, 2,3);
        dump(a);
        System.out.println("----------------");

        /**
         *  | 4  7 |
         *  | 5  8 |
         *  | 6  9 |
         */
        Matrix b = new Matrix(3,2);
        b.setValue(0,0,4);
        b.setValue(0,1,7);

        b.setValue(1,0,5);
        b.setValue(1,1,8);

        b.setValue(2,0,6);
        b.setValue(2,1,9);
        dump(b);

        System.out.println("-----------------");
        dump(multiply(a,b));
    }

    public static void dump(Matrix matrix){
        for(int i = 0; i < matrix.getRows();i++){
            for(int j = 0; j < matrix.getCols();j++){
                System.out.printf("%d ", matrix.getValue(i,j));
            }
            System.out.println();
        }
    }

    public static Matrix multiply(Matrix a,Matrix b){
        if(a.getCols() != b.getRows()){
            throw new IllegalArgumentException("rows/cols mismatch.");
        }
        Matrix result = new Matrix(a.getRows(), b.getCols());
        for(int i = 0; i < a.getRows(); i++){
            for(int j = 0; j< b.getCols(); j++){
                for(int k = 0; k < a.getCols(); k++){
                    result.setValue(i,j,result.getValue(i,j) +
                            a.getValue(i,k) * b.getValue(k,j));
                }
            }
        }
        return result;
    }
}
main

Fork/Join 框架最佳实践

1. 对一个任务调用 fork 方法会阻塞调用方, 直到该任务做出结果. 有必要在两个子任务的计算都开始之后再调用它. 因为会导致每个子任务都必须等待另一个完成才能启动.

2. 不应该在 RecursiveTask 任务内部使用 ForkJoinPool 的 invoke 方法, 相反应始终直接调用 compute 或 fork 方法,只有顺序代码才应该用 invoke 来启动并行计算.

3. 不要同时对左边和右边的子任务调用 fork 方法以排进 ForkJoinPool, 要直接对其中一个调用 compute。 线程重用.

4. 不好IDE 调试,调用 compute的线程并不是概念上的调用方,后者是调用 fork 的那个.

5. 测试要注意预热。不一定比顺序执行的快.


 

posted @ 2023-07-30 15:10  君子之行  阅读(17)  评论(0)    收藏  举报