簡體   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