我应该如何在 java 中中断和处置一组线程?



我正在编写一个在游戏状态下执行alhpa-beta搜索的游戏引擎,我正在尝试并行化它。到目前为止,我所拥有的是起初的工作,然后它似乎慢慢停止了。我怀疑这是因为我没有正确处理我的线程。

与计算机对战时,游戏会调用多线程计算机播放器对象的 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;
}
}

我需要能够在时间结束时中断所有线程 - 我应该如何实现这一点?到目前为止,我不断捕获(并忽略)中断异常。

Thread.stop 被弃用是有原因的。 当您在中间中断线程时,该线程没有机会正确释放它正在使用的资源,并且不会通知其他线程其完成...这在多线程应用程序中非常重要。 我对你的性能坦克并不感到惊讶;我敢打赌你的内存使用量会飙升。 您也不会回收线程,您可以在不创建新对象的情况下启动和停止它们,这意味着变量留下的任何损坏状态可能仍然困扰着它们。

更好的方法是设置一个标志,告诉线程它应该返回。 因此,在你的 WeightedMultiThread 类中包含一个名为 shouldQuit 的布尔值,并在每次调用 start() 时将其设置为 false。 然后,不要使用 while (true) do while (!shouldQuit),而不是 t.stop(),而是使用 t.shouldQuit = true。 对每个线程执行此操作后,使用另一个循环来检查每个线程的 t.isAlive(),一旦每个线程返回,就可以开始您的业务。 这样你应该有更好的结果。

这看起来像是使用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);
}
}

更好的是创建一次ExecutorService,并在每次调用getMove时重复使用它。创建线程的成本很高,因此如果可以的话,最好只执行一次。如果采用此方法,则不应调用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;
}

这可能更干净,但你明白了。

最敏感的方式是使用中断机制。Thread.interrupt()Thread.isInterrupted()方法。这可以确保您的消息将被传递到线程,即使它位于阻塞调用中(还记得一些方法声明抛出InterruptedException吗?

附言阅读 Brian Goetz 的"Java 并发实践"第 7 章:取消和关闭会很有用。

最新更新