我正在编写一个在游戏状态下执行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。
答案 0 :(得分:6)
Thread.stop因某种原因而被弃用。当您在中间中断线程时,线程没有机会正确释放它正在使用的资源,并且不会通知其他线程完成它...这在多线程应用程序中非常重要。我对你的表演坦克并不感到惊讶;我愿意打赌你的记忆用法在屋顶上拍摄。你也不回收线程,你可以在不创建新对象的情况下启动和停止它们,这意味着变量留下的任何破坏状态可能仍然困扰着它们。
更好的方法是设置一个标志,告诉线程它应该返回。因此,在WeightedMultiThread类中包含一个名为shouldQuit的布尔值,并在每次调用start()时将其设置为false。然后,而不是while(true)do while(!shouldQuit),而不是t.stop(),而不是t.shouldQuit = true。在对每个线程执行此操作之后,请使用另一个循环来检查每个线程的t.isAlive(),并在每个线程返回后,继续执行您的业务。你应该有更好的结果。
答案 1 :(得分:3)
这看起来像是使用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
评估代码中对其进行编码,例如< / p>
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;
}
这可能更干净,但你明白了。
答案 2 :(得分:0)
最敏感的方法是使用中断机制。 Thread.interrupt()
和Thread.isInterrupted()
方法。这可以确保您的消息将被传递到线程,即使它位于阻塞调用内(请记住一些方法声明抛出InterruptedException
?)
P.S。阅读Brian Goetz的"Java Concurrency in Practice"第7章:取消和关闭会很有用。