Java:让ExecutorService产生可重复的行为?

时间:2014-01-15 08:12:41

标签: java multithreading concurrency threadpool worker

我一直在尝试并行化代码中的一部分方法(如Example类的 function_to_parallelize(...)方法所示)。我检查了遗嘱执行人框架,发现期货和期货可以使用Callables创建几个最终返回值的工作线程。但是,通常在执行程序框架中显示的在线示例非常简单,并且它们似乎都没有遇到我需要在类中包含我尝试并行化的代码的方法的特定情况。根据一个Stackoverflow线程,我设法编写一个实现Callable的外部类,称为Solver,实现该方法 call()并设置执行器框架,如方法 function_to_parallelize( ...)即可。在每个工作线程中发生的一些计算需要对Example类的数据成员进行操作的方法* subroutine_A(...)*(此外,这些子例程中的一些对各种采样函数使用随机数)。

我的问题是当我的程序执行并产生结果(有时是准确的,有时不是)时,每次运行它时,各种工作线程的组合计算结果都是不同的。我认为它必须是共享内存问题,所以我输入了Example类的每个数据成员的Solver构造函数副本,包括包含Random rng的实用程序。此外,我将我需要的子程序直接复制到Solver类中(即使它能够在没有这个的情况下从Example调用这些方法)。为什么我每次都会获得不同的价值?我需要实现一些东西,比如锁定机制还是同步?

或者,是否有更简单的方法将一些并行化注入到该方法中?重写“示例”类或彻底改变我的类结构不是一个选项,因为我需要它的当前形式用于我的软件/系统的各种其他方面。

下面是我的代码小插图(好吧,这是一个令人难以置信的抽象/简化形式,以便向您展示基本结构和目标区域,即使它比平常的小插图长一点):

public class Tools{
    Random rng;
    public Tools(Random rng){
        this.rng = rng;
    }...
}
public class Solver implements Callable<Tuple>{
    public Tools toolkit;
    public Item W;
    public Item v;
    Item input;
    double param;

    public Solver(Item input, double param, Item W, Item v, Tools toolkit){
        this.input = input;
        this.param = param;
        //...so on & so forth for rest of arguments
    }       
    public Item call() throws Exception {
        //does computation that utilizes the data members W, v
        //and calls some methods housed in the "toolkit" object
    }       
    public Item subroutine_A(Item in){....}
    public Item subroutine_B(Item in){....}
}
public class Example{
    private static final int NTHREDS = 4;
    public Tools toolkit;
    public Item W;
    public Item v;

    public Example(...,Tools toolkit...){
        this.toolkit = toolkit; ...
    }
    public Item subroutine_A(Item in){
        // some of its internal computation involves sampling & random # generation using
        // a call to toolkit, which houses functions that use the initialize Random rng
        ...
    }
    public Item subroutine_B(Item in){....}
    public void function_to_parallelize(Item input, double param,...){
        ExecutorService executor = Executors.newFixedThreadPool(NTHREDS);
        List<Future<Tuple>> list = new ArrayList<Future<Tuple>>();      
        while(some_stopping_condition){
            // extract subset of input and feed into Solver constructor below
            Callable<Tuple> worker = new Solver(input, param, W, v, toolkit);
            Future<Tuple> submit = executor.submit(worker);
            list.add(submit);
        }
         for(Future<Tuple> future : list){
            try {
                Item out = future.get();
                // update W via some operation using "out" (like multiplying matrices for example)
            }catch(InterruptedException e) {
                e.printStackTrace();
            }catch(ExecutionException e) {
                e.printStackTrace();
            }
        }
        executor.shutdown(); // properly terminate the threadpool
    }
}

ADDENDUM:虽然下面的flob的回答确实解决了我的插图/代码的问题(你应该确保你设置你的代码等待所有线程赶上.await() ),这个问题在我做出这个修正后没有消失。事实证明,问题在于Random如何使用线程。实质上,线程以各种顺序(通过OS /调度程序)进行调度,因此不会重复每次运行程序时执行它们的顺序,以确保获得纯粹确定性的结果。我检查了线程安全版本的Random(并使用它来获得更高的效率)但是它不允许你设置种子。但是,我强烈建议那些希望在其线程工作者中加入随机计算的人将其用作多线程工作的RNG。

1 个答案:

答案 0 :(得分:1)

我看到的问题是你在更新W之前不等待所有任务完成,因为一些Callable实例将获得更新的W而不是你期望的那个

此时即使并非所有任务都已完成,W也会更新

  

块引用    //使用“out”(例如乘法矩阵)通过某些操作更新W

未完成的任务将采用上面更新的W而不是您期望的

快速解决方案(如果你知道你将拥有多少Solver任务)将使用CountDownLatch来查看所有任务何时完成:

 public void function_to_parallelize(Item input, double param,...){
    ExecutorService executor = Executors.newFixedThreadPool(NTHREDS);
    List<Future<Tuple>> list = new ArrayList<Future<Tuple>>();

    CountDownLatch latch = new CountDownLatch(<number_of_tasks_created_in_next_loop>);

    while(some_stopping_condition){
        // extract subset of input and feed into Solver constructor below

        Callable<Tuple> worker = new Solver(input, param, W, v, toolkit,latch);

        Future<Tuple> submit = executor.submit(worker);
        list.add(submit);
    }
     latch.await();
     for(Future<Tuple> future : list){
        try {
            Item out = future.get();
            // update W via some operation using "out" (like multiplying matrices for example)
        }catch(InterruptedException e) {
            e.printStackTrace();
        }catch(ExecutionException e) {
            e.printStackTrace();
        }
    }
    executor.shutdown(); // properly terminate the threadpool
}

然后在Solver类中,当调用方法结束时,你必须减少锁存器:

public Item call() throws Exception {
    //does computation that utilizes the data members W, v
    //and calls some methods housed in the "toolkit" object

   latch.countDown();
}