简体   繁体   中英

Depth first search in parallel

I am implementing a DFS to find the exit of a maze, and currently it is single threaded.

I am planning on making it more efficient by creating several threads that search the tree using the same single-threaded algorithm, but I am randomizing which direction to go in when I encounter an intersection.

For example, the threads encounter an intersection where they can go East or West. Half of them go East and half go West. This continues until one of the threads finds the solution path.

Is this a valid way to implement DFS in a parallel way?

If you do recursive parallel work in Java, use the Fork and Join API introduced in Java 7.

public class MazeNode {
  // This class represents a Path from the start of your maze to a certain node. It can be a) a dead end, b) the exit, c) have child Paths
  ...
}

public class MazeTask extends RecursiveTask<MazeNode>
{
  private MazeNode node;

  MazeTask(MazeNode node) {
    this.node = node;
  }


  // Returns null or the exit node
  @Override
  protected MazeNode compute() {    
  if (node.isDeadEnd())
    return null;
  else if (node.isExit())
    return node;
  else { // node has ways to go
    // implement as many directions as you want
    MazeTask left = new MazeTask(node.getLeft());
    MazeTask right = new MazeTask(node.getRight());

    left.fork(); // calculate in parallel

    MazeNode rightNode = right.compute(); // calculate last Task directly to save threads
    MazeNode leftNode = left.join(); // Wait for the other task to complete and get result

    if (rightNode != null)
      return rightNode;
    else
      return leftNode; // This assumes there is only one path to exit
  }
}


public static void main(String[] args) {
  MazeNode maze = ...
  MazeNode exit = new ForkJoinPool().invoke(new MazeTask(maze));
}

[UPDATE1]

Here is my proposal with thread synchronization (but based on our discussion with @IraBaxter I'm not sure anymore that my way gives any advantages):

When algorithm starts only one thread is needed until you get to first fork. When this one thread gets there it should put all possible outcomes (left, right, middle) to stack and stop itself. Then since there are some elements in stack several threads are activated to start from edges stored in stack. When each of these threads reaches a fork all outcomes are put to the stack and threads stop themselves (not all at once, each does when it needs) and take edges from stack. And so on. Every time when any thread is stopped (regardless because of fork or dead end) it switched to mode waiting for edges in stack (or takes one if stack is not empty). And every time when some edge is added to the stack threads are notified about non-emptiness of the stack.

I used term "edge" here in a meaning of position of fork plus direction where to go from given fork. Stack gives you depth-first property of algorithm.

PS: It's possible to optimize this approach by reducing number of synchronization points. We can collect forking edges in separate worklist for every thread and don't stop this thread until it reaches dead-end. We exclude from this worklist those edges where this thread decided to go on each fork. Then we migrate local worklist to global one when thread reaches dead-end. So synchronization is used for idle threads to start from points from global worklist.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM