2

一般的な質問

JavaDocsおよびNiklas Schlimm によるこのブログに記載されているように、Phaser を使用してすべてのタスクの開始時刻を同期できることはよく知られています。

Niklas は、同期の非常にわかりやすいイメージを描きました。

         |Phaser   |Phaser   |Phaser   |  
Task 1   | ------> | ------> | ------> | ...
Task 2   | ------> | ------> | ------> | ...
...

ここで、タスクの階層があるとします。

         |Phaser   |Phaser   |Phaser   |Phaser   |Phaser   |Phaser   |Phaser   |   ...
Master   |         |         | ------> |         |         | ------> |         |   ...
Task 1.1 | ----------------> |         | ----------------> |         | ----------> ...
Task 1.2 | ----------------> |         | ----------------> |         | ----------> ...
...      |         |         |         |         |         |         |         |   ...
Task 2.1 | ------> |         |         | ------> |         |         | ------> |   ...
Task 2.2 | ------> |         |         | ------> |         |         | ------> |   ...
...      |         |         |         |         |         |         |         |   ...
Task 3.1 |         | ------> |         |         | ------> |         |         |   ...
Task 3.2 |         | ------> |         |         | ------> |         |         |   ...
...      |         |         |         |         |         |         |         |   ...

したがって、依存関係のツリーは次のようになります。

                      Master
           /-----------/  \-----------\
           |                        Task 2 
         Task 1                       |
           |                        Task 3
           \-----------\  /-----------/
                      Master'

一般的に、解決すべき依存関係のツリーがあります (たとえば、ゲーム パイプラインでは、AI / ゲーム ロジック / レンダリング タスクなどがあります)。幸いなことに、「大きな」同期ポイントがあり、ツリーは固定されています (ただし、パーティの数は固定されていません)。いくつかのフェイザーで解決するのは簡単です。しかし、フェイザーを 1 つだけ使用することは可能ですか?

1つの特別なケース

具体的には、以下の問題を解決するプログラムを作成しました。

         |phasers[0]|phasers[1]|phasers[2]|phasers[0]|phasers[1]|phasers[2]| ...
Task 1   | -------> |          |          | -------> |          |          | ...
Task 2   | -------> |          |          | -------> |          |          | ...
Task 3   |          |          | -------> |          |          | -------> | ...
Task 4   |          | -------> |          |          | -------> |          | ...

ここにコード:

public class VolatileTester {

    private int a = 0, b = 0;       // change to volatile here
    private int c = 0;

    private final int TEST_COUNT = 100_000;
    private int[] testResult = new int[TEST_COUNT];

    private static void printResult(int[] result) {
        final Map<Integer, Integer> countMap = new HashMap<>();
        for (final int n : result) {
            countMap.put(n, countMap.getOrDefault(n, 0) + 1);
        }

        countMap.forEach((n, count) -> {
            System.out.format("%d -> %d%n", n, count);
        });
    }

    private void runTask1() {
        a = 5;
        b = 10;
    }

    private void runTask2() {
        if (b == 10) {
            if (a == 5) {
                c = 1;
            } else {
                c = 2;
            }
        } else {
            if (a == 5) {
                c = 3;
            } else {
                c = 4;
            }
        }
    }

    private void runTask3() {
        // "reset task"
        a = 0;
        b = 0;
        c = 0;
    }

    private static class PhaserRunner implements Runnable {
        private final Phaser loopStartPhaser;
        private final Phaser loopEndPhaser;
        private final Runnable runnable;

        public PhaserRunner(Phaser loopStartPhaser, Phaser loopEndPhaser, Runnable runnable) {
            this.loopStartPhaser = loopStartPhaser;
            this.loopEndPhaser = loopEndPhaser;
            this.runnable = runnable;
        }

        @Override
        public void run() {
            while (loopStartPhaser.arriveAndAwaitAdvance() >= 0) {
                runnable.run();
                loopEndPhaser.arrive();
            }
        }
    }

    void runTest() throws InterruptedException {
        final Phaser[] phasers = new Phaser[]{new Phaser(3), new Phaser(3), new Phaser(2)};

        final Thread[] threads = new Thread[]{
                // build tree of dependencies here
                new Thread(new PhaserRunner(phasers[0], phasers[1], this::runTask1)),
                new Thread(new PhaserRunner(phasers[0], phasers[1], this::runTask2)),
                new Thread(new PhaserRunner(phasers[2], phasers[0], this::runTask3))
        };

        try {
            for (Thread thread : threads) {
                thread.start();
            }

            phasers[0].arrive();        // phaser of last round

            for (int i = 0; i < TEST_COUNT; i++) {
                phasers[1].arriveAndAwaitAdvance();

                // Task4 here
                testResult[i] = c;

                phasers[2].arrive();
            }
        } finally {
            for (Phaser phaser : phasers) {
                phaser.forceTermination();
            }
        }

        for (Thread thread : threads) {
            thread.join();
        }

        printResult(testResult);
    }
}

複数の が使用されていることがわかりますPhaser。(上記のように)複数のフェイザーを保持する方が良いですか、それとも大きなフェイザーを 1 つだけ使用する方が良いですか? または、Javaで推奨される他の同期方法はありますか?

4

2 に答える 2

0

はい、1本で十分Phaserです。各サイクルの後に実行される がありますCyclicBarrier。をオーバーライドして同様の機能をサポートします。Runnable barrierActionPhaseronAdvance

特定のフェーズの最後のパーティーが到着すると、任意のアクションが実行され、フェーズが進行します。これらのアクションは、フェーズ アドバンスをトリガーするパーティによって実行され、終了も制御するメソッド onAdvance(int, int) をオーバーライドすることによって調整されます。このメソッドをオーバーライドすることは、CyclicBarrier にバリア アクションを提供することと似ていますが、より柔軟です。

だから本質的に

Phaser phaser = new Phaser() {
    protected boolean onAdvance(int phase, int parties) {
        // Signal Master thread to perform its task and wait for it to finish
    }
};
于 2016-05-11T08:54:08.323 に答える