繁体   English   中英

为fork-join递归添加停止条件

[英]Adding a stop condition to fork-join recursion

为了简化我的案例,让我们假设我正在使用Java的Fork-Join框架实现二进制搜索。 我的目标是在整数数组中找到特定的整数值(目标整数)。 这可以通过将数组减半直到其足够小以执行串行搜索来完成。 算法的结果必须是一个布尔值,指示是否在数组中找到目标整数。

幻灯片28及以后的克劳斯·克雷夫特(Klaus Kreft)的演讲也探讨了类似的问题。 但是,Kreft的目标是在阵列中找到最大的数字,因此必须扫描所有条目。 在我的情况下,不必扫描整个数组,因为一旦找到目标整数,就可以停止搜索。

我的问题是,一旦遇到目标整数,许多任务已经插入到线程池中,并且由于没有必要继续搜索,因此需要取消它们。 我试图从RecursiveTask内部调用getPool()。terminate(),但是这样做没有太大帮助,因为许多任务已经排队,甚至我注意到即使调用了shutdown之后,新的队列也排队了。

我当前的解决方案是使用以'false'初始化的静态易失性布尔值,并在任务开始时检查其值。 如果仍然为“ false”,则任务开始工作;如果为“ true”,则任务立即返回。 我实际上可以为此使用RecursiveAction。

因此,我认为该解决方案应该有效,但我想知道框架是否提供了某种标准的方式来处理类似情况-即为递归定义一个停止条件,从而取消所有排队的任务。

请注意,如果我想在找到目标整数后立即停止所有正在运行的任务(通过一个正在运行的任务之一),则必须在这些任务的每一行之后检查布尔值,因为这不能影响布尔值,所以会影响性能。已缓存(定义为易失性)。

因此,的确,我认为需要一些标准解决方案,并且可以以清除队列和间断正在运行的任务的形式提供。 但是我还没有找到这样的解决方案,所以我想知道是否有人对此有所了解或有更好的主意。

谢谢您的时间,阿萨夫

编辑:这是我的测试代码:

package xxx;

import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;

public class ForkJoinTest {

    static final int ARRAY_SIZE = 1000;
    static final int THRESHOLD = 10;

    static final int MIN_VALUE = 0;
    static final int MAX_VALUE = 100;

    static Random rand = new Random();


    // a function for retrieving a random int in a specific range
    public static int randInt(int min, int max) {
        return rand.nextInt((max - min) + 1) + min;
    }

    static volatile boolean result = false;
    static int[] array = new int[ARRAY_SIZE];
    static int target;

    @SuppressWarnings("serial")
    static class MyAction extends RecursiveAction {

        int startIndex, endIndex;

        public MyAction(int startIndex, int endIndex) {
            this.startIndex = startIndex;
            this.endIndex = endIndex;
        }

        // if the target integer was not found yet: we first check whether 
        // the entries to search are too few. In that case, we perform a 
        // sequential search and update the result if the target was found. 
        // Otherwise, we break the search into two parts and invoke the 
        // search in these two tasks.
        @Override
        protected void compute() {
            if (!result) {
                if (endIndex-startIndex<THRESHOLD) { 
                    // 
                    for (int i=startIndex ; i<endIndex ; i++) {
                        if (array[i]==target) {
                            result = true;
                        }
                    }
                } else {
                    int middleIndex = (startIndex + endIndex) / 2;
                    RecursiveAction action1 = new MyAction(startIndex, middleIndex);
                    RecursiveAction action2 = new MyAction(middleIndex+1, endIndex);
                    invokeAll(Arrays.asList(action1,action2));
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        for (int i=0 ; i<ARRAY_SIZE ; i++) {
            array[i] = randInt(MIN_VALUE, MAX_VALUE);
        }
        target = randInt(MIN_VALUE, MAX_VALUE);
        ForkJoinPool pool = new ForkJoinPool();
        pool.invoke(new MyAction(0,ARRAY_SIZE));
        System.out.println(result);
    }

}

我认为您可能在为正确的解决方案发明障碍。

您说您的boolean stop标记必须是volatile ,因此会干扰解决方案的速度-是的,是的,否-访问volatile确实会进行高速缓存刷新,但是您是否考虑过AtomicBoolean

我相信正确的解决方案是使用AtomicBoolean标志使所有进程停止。 您应该以合理的细粒度检查,以使系统快速停止。

尝试清除所有队列并中断所有线程将是一个错误-这将导致可怕的混乱。

    static AtomicBoolean finished = new AtomicBoolean();
    ....

        protected void compute() {
            if (!finished.get()) {
                if (endIndex - startIndex < THRESHOLD) {
                    //
                    for (int i = startIndex; i < endIndex && !finished.get(); i++) {
                        if (array[i] == target) {
                            finished.set(true);
                            System.out.print("Found at " + i);
                        }
                    }
                } else {
                    ...
                }
            }
        }

我在上面留下了关于如何通过查看在许多内置功能中实现此功能的开源产品来进行此操作的评论。 让我在这里详细说明一下。

如果要取消开始或当前正在执行的任务,则每个任务都需要了解其他每个任务。 当一个任务找到所需的内容时,该任务需要通知其他所有任务停止。 您无法使用二元递归除法(RecursiveTask等)来执行此操作,因为您以递归方式创建新任务,而旧任务永远不会知道新任务。 我确信您可以为每个新任务传递对stop-me字段的引用,但是它将变得非常混乱,并且调试将“很有趣”。

您可以使用Java8 CountedCompleter()完成此操作。 框架被屠宰来支持此类,因此框架应该完成的事情需要手动完成,但是可以工作。

每个任务都需要一个易失的布尔值和将其设置为true的方法。 每个任务都需要对所有其他任务的引用数组。 首先创建所有任务,每个任务都有一个空数组,它们将成为对其他任务的引用。 填写对其他所有任务的引用数组。 现在提交每个任务(请参阅此类的文档,fork()addPendingCount()等)。

当一个任务找到所需的内容时,它将使用对其他任务的引用数组将其布尔值设置为true。 如果存在具有多个线程的竞争条件,则没有关系,因为所有线程都设置为“ true”。您还需要处理tryComplete(),onCompletion()等。此类非常混乱。 它用于Java8流处理,这本身就是一个故事。

您无法做的是在双端队列开始之前清除待处理的任务。 您需要等待任务开始,然后检查布尔值是否为true。 如果执行时间很长,那么您可能还需要定期检查布尔值是否为true。 易失性读取的开销并没有那么糟,而且实际上没有其他方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM