移相器同步用法 [英] Phaser Synchronization Usage

查看:32
本文介绍了移相器同步用法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

众所周知,如JavaDocs此博客 作者 Niklas Schlimm.

It is well known that Phaser can be used to synchronize the start time of all tasks as mentioned in the JavaDocs and this blog by Niklas Schlimm.

Niklas 绘制了一幅非常容易理解的同步图像:

Niklas has drawn a pretty understandable image of the synchronization:

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

现在假设有一个任务层次结构:

Now supposed there is a hierarchy of tasks:

         |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/游戏逻辑/渲染任务).幸运的是有一个大"同步点并且树是固定的(但不是参与方的数量).用几个移相器解决是微不足道的.但是可以只使用一个移相器吗?

In a general case, there is a tree of dependencies to solve (let's say in a game pipeline, some are AI / game logic / render tasks). Luckily there is a "big" synchronization point and the tree is fixed (but not the number of parties). It is trivial to solve with several phasers. But is it possible to use only one phaser?

具体来说,我做了一个程序来解决以下问题.

Specifically, I made a program to solve the following problem.

         |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.保留多个移相器(如上)还是只使用一个大移相器更好?或者推荐其他 Java 同步方法?

You can see that multiple Phasers are used. Is it better to keep multiple phasers (like above), or just use one big phaser? Or any other synchronization methods in Java recommended?

推荐答案

应用程序中的所有任务都以逐步的方式进行,这意味着单个移相器就足够了.要求是任务可以循环跳过阶段,例如,对于每三个阶段,给定的任务应该运行两个阶段,然后跳过一个阶段(一个阶段空闲).总之,

All tasks in your application proceed in a step wise manner, which means a single phaser would suffice. The requirement is tasks can skip phases cyclically, e.g., for every three phases, the given task should run two phases and then skips one phase (is idle for one phase). In summary,

  • 任务应该在每一步工作之前执行 arriveAndAwaitAdvance().
  • 任务应该只调用 arriveAndAwaitAdvance() 来跳过一个阶段.
  • A task should execute arriveAndAwaitAdvance() before each step of work.
  • A task should just invoke arriveAndAwaitAdvance() to skip a phase.

为此,每个任务都可以使用一个布尔数组,在名为 enabled 的示例中,它指定是否在给定的阶段编号启用它.通过使用模代数(enabled[phase % enabled.length]),我们可以定义循环模式.例如,要指定任务应该运行三分之一,我们将 enabled 声明为 new boolean[]{true, false, false}.

To this end, each task can use a boolean array, in the example called enabled, that specifies if it is enabled at a given phase number. By using modular algebra (enabled[phase % enabled.length]) we can define cyclic patterns. For instance, to specify that a task should run one out of three ticks, we declare enabled as new boolean[]{true, false, false}.

请记住,无论是否执行任何实际工作,任务都必须推进阶段.

我相应地修正了你的例子:

I fixed your example accordingly:

import java.util.concurrent.*;
import java.util.*;

public class VolatileTester {

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

    private final int TEST_COUNT = 100;
    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 phaser;
        private final Runnable runnable;
        private boolean[] enabled;

        public PhaserRunner(Phaser phaser, boolean[] enabled, Runnable runnable) {
            this.phaser = phaser;
            this.runnable = runnable;
            this.enabled = enabled;
        }

        @Override
        public void run() {
            int phase;
            for (;;) {
                phase = phaser.arriveAndAwaitAdvance();
                if (phase < 0) {
                    break;
                } else if (enabled[phase % enabled.length]) {
                    System.out.println("I'm running: " + Thread.currentThread());
                    runnable.run();
                }
            }
        }
    }

    public void runTest() throws InterruptedException {
        final Phaser phaser = new Phaser(4);

        final Thread[] threads = new Thread[]{
                // build tree of dependencies here
                new Thread(new PhaserRunner(phaser, new boolean[]{true, false, false}, this::runTask1), "T1"),
                new Thread(new PhaserRunner(phaser, new boolean[]{false, false, true}, this::runTask2), "T2"),
                new Thread(new PhaserRunner(phaser, new boolean[]{false, true, false}, this::runTask3), "T3")
        };

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

            for (int i = 0; i < TEST_COUNT; i++) {
                testResult[i] = c;
                phaser.arriveAndAwaitAdvance();
            }
        } finally {
            phaser.forceTermination();
        }

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

        printResult(testResult);
    }

    public static void main(String[]args) throws Exception {
        new VolatileTester().runTest();
    }
}

这篇关于移相器同步用法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆