1

ゲームの状態でalhpa-beta検索を実行するゲームエンジンを作成していて、それを並列化しようとしています。私が今まで持っていたものは、最初は機能していますが、その後、ゆっくりと停止するようです。これは、スレッドを正しく処理していないためだと思います。

コンピューターと対戦する場合、ゲームはMultiThreadedComputerPlayerオブジェクトのgetMove()関数を呼び出します。そのメソッドのコードは次のとおりです。

public void getMove(){
    int n = board.legalMoves.size();
    threadList = new ArrayList<WeightedMultiThread>();
    moveEvals = new HashMap<Tuple, Integer>();

    // Whenever a thread finishes its work at a given depth, it awaits() the other threads
    // When all threads are finished, the move evaluations are updated and the threads continue their work.
    CyclicBarrier barrier = new CyclicBarrier(n, new Runnable(){
        public void run() {
            for(WeightedMultiThread t : threadList){
                moveEvals.put(t.move, t.eval);
            }
        }
    });

    // Prepare and start the threads
    for (Tuple move : board.legalMoves) {
        MCBoard nextBoard = board.clone();
        nextBoard.move(move);
        threadList.add(new WeightedMultiThread(nextBoard, weights, barrier));
        moveEvals.put(move, 0);
    }
    for (WeightedMultiThread t : threadList) {t.start();}

    // Let the threads run for the maximum amount of time per move
    try {
        Thread.sleep(timePerMove);
    } catch (InterruptedException e) {System.out.println(e);}
    for (WeightedMultiThread t : threadList) {
        t.stop();
    }

    // Play the best move
    Integer best = infHolder.MIN;
    Tuple nextMove = board.legalMoves.get(0);
    for (Tuple m : board.legalMoves) {
        if (moveEvals.get(m) > best) {
            best = moveEvals.get(m);
            nextMove = m;
        }
    }
    System.out.println(nextMove + " is the choice of " + name + " given evals:");
    for (WeightedMultiThread t : threadList) {
        System.out.println(t);
    }
    board.move(nextMove);
}

そしてここで問題のスレッドのrun()メソッド:

public void run() {
    startTime = System.currentTimeMillis();
    while(true) {
        int nextEval = alphabeta(0, infHolder.MIN, infHolder.MAX);
        try{barrier.await();} catch (Exception e) {}
        eval = nextEval;
        depth += 1;
    }
}

時間切れになったときにすべてのスレッドを中断できるようにする必要があります-これをどのように実装する必要がありますか?現在のところ、InterruptedExceptionsを常にキャッチ(および無視)しています。

4

3 に答える 3

7

Thread.stopは、理由により非推奨になりました。途中でスレッドを中断すると、スレッドは使用していたリソースを適切に解放する機会がなく、他のスレッドにその完了を通知しません...マルチスレッドアプリで非常に重要なことです。私はあなたのパフォーマンスタンクに驚いていません。私はあなたのメモリ使用量が屋根を突き抜けることに賭けるつもりです。また、スレッドをリサイクルせず、新しいオブジェクトを作成せずにスレッドを開始および停止します。つまり、変数が残っていた壊れた状態が、おそらくまだスレッドを悩ませていることを意味します。

より良い方法は、スレッドに返す必要があることを通知するフラグを設定することです。したがって、WeightedMultiThreadクラスにshouldQuitのような名前のブール値を含め、start()が呼び出されるたびにfalseに設定します。次に、while(true)の代わりにwhile(!shouldQuit)を実行し、t.stop()の代わりにt.shouldQuit=trueを使用します。すべてのスレッドに対してこれを行った後、各スレッドでt.isAlive()をチェックする別のループを作成し、すべてのスレッドが戻ったら、ビジネスに取り掛かります。そうすれば、はるかに良い結果が得られるはずです。

于 2012-11-27T07:02:45.957 に答える
4

これは、を使用するのに理想的な場所のように見えますExecutorService。並列タスクを実装するインスタンスを作成Callableし、それらをに送信してからExecutorService、を使用awaitTerminationしてタイムアウトを強制できます。

例えば:

public void getMove() {
    ExecutorService service = Executors.newFixedThreadPool(board.legalMoves.size());
    List<Future<Something>> futures = new ArrayList<Future<Something>>(board.legalMoves.size());
    for (Tuple move : board.legalMoves) {
        futures.add(service.submit(new WeightedMultiThread(...)));
    }
    service.awaitTermination(timePerMove, TimeUnit.MILLISECONDS);
    service.shutdownNow(); // Terminate all still-running jobs
    for (Future<Something> future : futures) {
        if (future.isDone()) {
            Something something = future.get();
            // Add best move logic here
        }
    }
    ...
}

Something評価された移動に関する情報をカプセル化したものに置き換えます。とそれに関連するスコアSomethingを保持するクラスになることをお勧めします。TupleあなたのWeightedMultiThreadクラスはこのようなことをすることができます:

class WeightedMultiThread implements Callable<Something> {
    public Something call() {
        // Compute score
        ...
        // Return an appropriate data structure
        return new Something(tuple, score);
    }
}

さらに良いのは、ExecutorServiceonceを作成し、への呼び出しごとにそれを再利用することですgetMove。スレッドの作成には費用がかかるため、可能であれば1回だけ作成することをお勧めします。このアプローチを採用する場合は、を呼び出すshutdownNowのではなく、メソッドを使用してFuture.cancel、時間内に完了していないジョブを終了する必要があります。WeightedMultiThread実装がスレッドの中断をチェックし、をスローすることを確認してくださいInterruptedException。これは通常、中断可能である必要がある長時間実行タスクを作成するための良い方法です。

編集:

ゲーム空間のレベルごとの調査を行っているので、評価コードでgetMoveはなく関数でエンコードすることをお勧めします。Tuple

public Tuple getMove() {
    ExecutorService service = ...
    Tuple best = null;
    long timeRemaining = MAX_TIME;
    for (int depth = 0; depth < MAX_DEPTH && timeRemaining > 0; ++depth) {
        long start = System.currentTimeMillis();
        best = evaluateMoves(depth, service, timeRemaining);
        long end = System.currentTimeMillis();
        timeRemaining -= (end - start);
    }
    return best;
}

private Tuple evaluateMoves(int depth, ExecutorService service, long timeRemaining) {
    List<Future<Whatever>> futures = service.submit(...); // Create all jobs at this depth
    service.awaitTermination(timeRemaining, TimeUnit.MILLISECONDS);
    // Find best move
    ...
    return best;
}

それはおそらくもっときれいかもしれませんが、あなたはその考えを理解します。

于 2012-11-27T07:19:09.020 に答える
0

最も敏感な方法は、中断メカニズムを使用することです。Thread.interrupt()Thread.isInterrupted()メソッド。これにより、メッセージがブロッキング呼び出し内にある場合でも、メッセージがスレッドに配信されることが保証されます(一部のメソッドがスローを宣言していることを覚えていますInterruptedExceptionか?)

PS Brian Goetzの「JavaConcurrencyinPractice」第7章:キャンセルとシャットダウンを読むと便利です。

于 2012-11-27T13:32:04.027 に答える