2

私たちのアプリケーションでは、定義された時点ですべてのワーカー スレッドを同期する必要があります。これには を使用しますがCyclicBarrier、うまくスケーリングできないようです。スレッドが 8 つを超えると、同期のオーバーヘッドがマルチスレッドの利点を上回るようです。(ただし、測定データでは対応できません。)

EDIT : 同期は、100k から 1M 回のオーダーで非常に頻繁に行われます。

多くのスレッドの同期が「難しい」場合、同期ツリーの構築に役立ちますか? スレッド 1 は 2 と 3 を待機し、スレッド 2 と 3 はそれぞれ 4+5 と 6+7 を待機します。終了後、スレッド 2 と 3 はスレッド 1 を待ち、スレッド 4 と 5 はスレッド 2 を待ちます。

1
| \
2   3
|\  |\
4 5 6 7

そのようなセットアップは、同期のオーバーヘッドを削減しますか? アドバイスをいただければ幸いです。

この注目の質問も参照してください: Java で最速の周期的同期は何ですか (ExecutorService 対 CyclicBarrier 対 X)?

4

4 に答える 4

2

スレッドが 8 つを超えると、同期のオーバーヘッドがマルチスレッドの利点を上回るようです。(ただし、測定データでは対応できません。)

正直なところ、そこに問題があります。パフォーマンス ベンチマークを計算し、これが問題であることを証明するか、まったく間違った問題を解決するために何時間も何日も費やす危険を冒してください。

于 2012-09-28T01:23:13.953 に答える
1

私の理解が正しければ、解決策をいくつかの部分に分割し、それらを別々に解決しようとしていますが、同時に解決しようとしていますよね? 次に、現在のスレッドにそれらのタスクを待機させますか? fork/join パターンのようなものを使用したい。

List<CustomThread> threads = new ArrayList<CustomThread>();
for (Something something : somethings) {
    threads.add(new CustomThread(something));
}
for (CustomThread thread : threads) {
    thread.start();
}
for (CustomThread thread : threads) {
    thread.join(); // Blocks until thread is complete
}
List<Result> results = new ArrayList<Result>();
for (CustomThread thread : threads) {
    results.add(thread.getResult());
}
// do something with results.

Java 7 では、フォーク/ジョイン プールを介したサポートがさらに強化されています。ForkJoinPoolそのトレイルを参照し、Google を使用して他の多くのチュートリアルの 1 つを見つけてください。

この概念を再帰して、必要なツリーを取得できます。作成したスレッドにまったく同じ方法でさらにスレッドを生成させるだけです。


編集:それほど多くのスレッドを作成しないという印象を受けていたので、これはシナリオに適しています。例はひどく短くはありませんが、スレッドではなくジョブを待つことができるという、他の回答での議論と同じ流れに沿っています。

まず、 を受け取ってを返すCallableサブジョブ用のが必要です。InputResult

public class SubJob implements Callable<Result> {
    private final Input input;

    public MyCallable(Input input) {
        this.input = input;
    }

    public Result call() {
        // Actually process input here and return a result
        return JobWorker.processInput(input);
    }
}

それを使用するにExecutorServiceは、固定サイズのスレッド プールを使用して を作成します。これにより、同時に実行するジョブの数が制限されるため、誤ってシステムをスレッド爆撃することはありません。あなたの主な仕事は次のとおりです。

public class MainJob extends Thread {

    // Adjust the pool to the appropriate number of concurrent
    // threads you want running at the same time
    private static final ExecutorService pool = Executors.newFixedThreadPool(30);
    private final List<Input> inputs;

    public MainJob(List<Input> inputs) {
        super("MainJob")
        this.inputs = new ArrayList<Input>(inputs);
    }

    public void run() {
        CompletionService<Result> compService = new ExecutorCompletionService(pool);
        List<Result> results = new ArrayList<Result>();
        int submittedJobs = inputs.size();
        for (Input input : inputs) {
            // Starts the job when a thread is available
            compService.submit(new SubJob(input)); 
        }
        for (int i = 0; i < submittedJobs; i++) {
            // Blocks until a job is completed
            results.add(compService.take())
        }
        // Do something with results
    }
}

これにより、ジョブを実行するたびに一連の新しいスレッドを生成する代わりに、スレッドを再利用できます。完了サービスは、ジョブが完了するのを待っている間にブロックを行います。また、resultsリストは完成順となりますのでご注意ください。

Executors.newCachedThreadPoolを使用して、上限のないプールを作成することもできます( の使用と同様Integer.MAX_VALUE)。スレッドが使用可能な場合はスレッドを再利用し、プール内のすべてのスレッドがジョブを実行している場合は新しいスレッドを作成します。これは、後でデッドロックが発生し始めた場合に望ましい場合があります (固定スレッド プールで待機しているジョブが多すぎて、サブジョブを実行および完了できないため)。これにより、作成/破棄するスレッドの数が少なくとも制限されます。

最後にExecutorService、おそらくシャットダウン フックを介して手動でシャットダウンする必要があります。そうしないと、含まれるスレッドによって JVM を終了できなくなります。

それが役立つ/意味があることを願っています。

于 2012-09-27T22:51:30.533 に答える
1

あなたは、非常に悪いコーディングにつながる傾向がある微妙に間違った方法で問題について考えています。スレッドを待つのではなく、作業が完了するのを待ちます。

おそらく最も効率的な方法は、共有された待機可能なカウンターです。新しい作業を行うときは、カウンターをインクリメントしてカウンターに通知します。作業が完了したら、カウンターを減らします。用事がない場合はカウンターでお待ちください。カウンターがゼロになったら、新しい仕事ができるかどうかを確認します。

于 2012-09-27T22:42:26.093 に答える
0

(行列の列を処理する例のように)生成タスクがある場合は、CyclicBarrierでスタックする可能性があります。つまり、第2世代の作業を処理するために、第1世代のすべての作業を実行する必要がある場合、実行できる最善の方法は、その条件が満たされるのを待つことです。

各世代に数千のタスクがある場合は、それらのすべてのタスクをExecutorService(ExecutorService.invokeAll)に送信し、結果が返されるのを待ってから次のステップに進む方がよい場合があります。これを行うことの利点は、物理CPUが制限されているときに、コンテキストスイッチングと、数百のスレッドを割り当てることによる時間/メモリの浪費を排除することです。

タスクが世代別ではなく、サブセットで次のステップが発生する前にサブセットのみを完了する必要があるツリーのような構造である場合は、を検討することForkJoinPoolをお勧めします。Java7は必要ありません。それを行う。Java 6のリファレンス実装を入手できます。これは、JSRがForkJoinPoolライブラリコードを導入したものの下にあります。

また、Java6での大まかな実装を提供する別の回答があります。

public class Fib implements Callable<Integer> {
    int n;
    Executor exec;

    Fib(final int n, final Executor exec) {
        this.n = n;
        this.exec = exec;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Integer call() throws Exception {
        if (n == 0 || n == 1) {
            return n;
        }

        //Divide the problem
        final Fib n1 = new Fib(n - 1, exec);
        final Fib n2 = new Fib(n - 2, exec);

        //FutureTask only allows run to complete once
        final FutureTask<Integer> n2Task = new FutureTask<Integer>(n2);
        //Ask the Executor for help
        exec.execute(n2Task);

        //Do half the work ourselves
        final int partialResult = n1.call();

        //Do the other half of the work if the Executor hasn't
        n2Task.run();

        //Return the combined result
        return partialResult + n2Task.get();
    }

}    

タスクを分割しすぎて、各スレッドで実行される作業単位が小さすぎると、パフォーマンスに悪影響が及ぶことに注意してください。たとえば、上記のコードはフィボナッチを解くのに非常に遅い方法です。

于 2012-09-28T00:43:58.157 に答える