为了简化我的情况,我们假设我正在使用Java的Fork-Join框架实现二进制搜索。我的目标是在整数数组中找到一个特定的整数值(目标整数)。这可以通过将数组分成一半来完成,直到它足够小以执行串行搜索。算法的结果需要是一个布尔值,指示是否在数组中找到了目标整数。
在幻灯片28中的Klaus Kreft's presentation中探讨了类似的问题。但是,Kreft的目标是找到阵列中最大的数字,以便所有条目都必须被扫描。在我的情况下,没有必要扫描整个数组,因为一旦找到目标整数,搜索就可以停止。
我的问题是,一旦遇到目标整数,许多任务已经插入到线程池中,我需要取消它们,因为继续搜索是没有意义的。我试图从RecursiveTask中调用getPool()。terminate(),但由于很多任务已经排队,所以我甚至注意到即使在调用shutdown之后新的once也排队等等。
我目前的解决方案是使用静态易失性布尔值,该布尔值以' false'并在任务开始时检查其值。如果它仍然是“错误的”#39;然后任务开始工作,如果它真的',任务立即返回。我实际上可以使用RecursiveAction。
所以我认为这个解决方案应该可行,但是我想知道框架是否提供了一些处理这种情况的标准方法 - 即为递归定义一个停止条件,从而取消所有排队的任务。
请注意,如果我想在找到目标整数时立即停止所有正在运行的任务(通过其中一个正在运行的任务),我必须在这些任务中的每一行之后检查布尔值,这会影响性能,因为它的值boolean不能被缓存(它被定义为volatile)。
确实,我认为需要一些标准解决方案,并且可以以清除队列和插入正在运行的任务的形式提供。但我还没有找到这样的解决方案,我想知道是否有其他人知道它或者有更好的想法。
感谢您的时间, 阿萨弗
编辑:这是我的测试代码:
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);
}
}
答案 0 :(得分:0)
我认为你可能正在发明正确解决方案的障碍。
你说你的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 {
...
}
}
}
答案 1 :(得分:0)
我通过查看在许多内置函数中执行此操作的开源产品,在上面留下了关于如何执行此操作的评论。我在这里详细介绍一下。
如果要取消正在开始或正在执行的任务,则每个任务都需要了解其他所有任务。当一个任务找到它想要的东西时,该任务需要通知每个其他任务停止。你无法通过二元递归除法(RecursiveTask等)来实现这一点,因为你以递归方式创建新任务,旧任务永远不会知道新任务。我确信你可以将一个stop-me字段的引用传递给每个新任务,但它会变得非常混乱,调试会很“有趣”。
您可以使用Java8 CountedCompleter()执行此操作。该框架被宰杀以支持这个类,因此框架应该完成的事情需要手动完成,但它可以工作。
每个任务都需要一个volatile布尔值和一个将其设置为true的方法。每个任务都需要一组对所有其他任务的引用。预先创建所有任务,每个任务都有一个空数组的其他任务的参考。填写对每个其他任务的引用数组。现在提交每个任务(请参阅此类的文档,fork()addPendingCount()等。)
当一个任务找到它想要的东西时,它使用对其他任务的引用数组来将它们的布尔值设置为true。如果存在具有多个线程的竞争条件,则无关紧要,因为所有线程都设置为“true”。您还需要处理tryComplete(),onCompletion()等。此类非常混乱。它用于Java8流处理,这本身就是一个故事。
你不能做的是在它们开始之前从deques中清除待处理的任务。您需要等到任务开始并检查布尔值是否为true。如果执行很长,那么您可能还需要定期检查布尔值是否为true。易失性读取的开销并不是那么糟糕,实际上没有别的办法。