Parallel IT · Parallel Task

Parallel Task — Quick Start

Background motivating Parallel Task (ParaTask), plus examples illustrating its features.

1. Motivation and background

1.1 Why multi-threading?

There are two reasons programmers may wish to multi-thread their desktop applications: to improve performance and to improve responsiveness. ParaTask helps achieve both.

Improving performance. Parallel computing arrived on mainstream desktops as multi-core processors, because of the difficulty of maintaining improvements in uni-processor clock speed. Users see no performance improvement unless their applications are parallelised, and parallel programming is notoriously difficult — especially for correctness and high performance.

Improving responsiveness. Multi-threading is also necessary even on a uni-processor. Unresponsive applications — buttons that don’t respond, windows that grey out while busy — are typically not multi-threaded: a single thread does all the work.

1.2 Graphical user interfaces

Desktop applications interact through a graphical user interface (GUI) built from visual components — some for input (buttons, text fields), others showing status (labels, progress bars). These applications follow the event-driven paradigm, where execution flow is determined by events such as mouse clicks or messages from other threads.

In a typical multi-threaded GUI application, the most important thread — the GUI thread (the event dispatch thread in Java) — is solely responsible for accessing GUI components and responding to events. An event loop waits for events to arrive and dispatches them to the appropriate handler. Programmers must keep event handlers short so control returns quickly to the event loop; otherwise events backlog and the application appears to “freeze”. Time-consuming work must therefore be dispatched to a helper thread.

Because GUI toolkits are typically single-threaded, only the GUI thread may access GUI components. A helper thread must not touch GUI components directly; instead it posts an event to the GUI thread, which updates the GUI. ParaTask simplifies developing applications that are both responsive and performant.

2. Parallel Task

2.1 Model overview

Different task types are supported by ParaTask, unified in a single model:

  • One-off tasks — CPU-bound computations. When invoked, a single instance is enqueued to be executed start to finish by any processor.
  • Multi-tasks — multiple tasks for data parallelism, mapped to different processors.
  • I/O tasks — I/O-bound computations (e.g. background tasks waiting for events). They correspond to classical threads and avoid creating a backlog of ready-to-execute tasks.

2.2 Example 1: Hello, World!

The hello() method is a standard sequential method:

public static void hello(String name) {
    System.out.println("Hello from " + name);
}

The three task variants reuse it:

TASK public static void task_hello() { hello("Task"); }
TASK(*) public static void multi_hello() {
    hello("Multi-Task [subtask " + CurrentTask.relativeID() + "]");
}
IO_TASK public static void interactive_hello() { hello("I/O Task"); }

The only difference between them is the TASK modifier. Invoking them returns a TaskID; because execution is asynchronous, we synchronise by grouping the IDs and waiting:

TaskIDGroup g = new TaskIDGroup(3);
g.add(id1); g.add(id2); g.add(id3);
g.waitTillFinished();

The tasks execute asynchronously with their caller — statements following each invocation run before the task computation completes.

2.3 Example 2: Let’s get surfing!

This example accesses the web to illustrate the difference between task types (it requires a network connection). The sequential version runs each web access one after another on a single thread, so the total time is the sum of the individual times — the application would “freeze” for the whole duration.

One-off tasks wrap the sequential, thread-safe method:

TASK public static void webAccessTask(String address) { webAccess(address); }

Tasks are shared across a team of worker threads, so the total time becomes the longest sum encountered by any one worker.

Multi-tasks pass the entire collection, with each subtask removing a URL from a concurrent queue; subtasks gain group awareness (barriers, relative position):

TASK(*) public static void webAccessMulti(ConcurrentLinkedQueue<String> queue) {
    String s = null;
    while ((s = queue.poll()) != null) webAccess(s);
}

I/O tasks are best here because the work is not compute-intensive; each task maps to its own thread, so the total time approaches the longest single task:

IO_TASK public static void webAccessInteractiveTask(String address) { webAccess(address); }

2.4 Example 3: Building a house (GUI)

The final example is a GUI application that illustrates the “freeze” problem. In sequential mode the application freezes; in parallel mode the graphics update smoothly. When the build button is pressed in parallel mode:

TaskID id = houseApplet.buildTask(colorWalls, colorRoof);
notify(Build.this::finishedBuilding());

The notify clause specifies that finishedBuilding() runs on the thread that enqueued the task (the EDT), letting the EDT return to the event loop and stay responsive.

Nested parallelism and dependences. The sub-parts of the house must be built in order. Each task is decoupled and unaware of the others; ordering is expressed with the dependsOn clause:

TaskID idFoundation = buildFoundation(foundationTiles);
TaskID idWalls = buildWalls(wallSides) dependsOn(idFoundation);
TaskID idRoof = buildRoofTiles(roofTiles) dependsOn(idWalls);
TaskID idDoor = buildDoor(door) dependsOn(idRoof);
TaskID idWindows = buildWindows(windows) dependsOn(idRoof);
TaskID idSign = buildSign(sign) dependsOn(idDoor, idWindows);

Each task runs as soon as a worker is free and its dependences are met. Changing the ordering only requires editing the dependsOn clauses — the task code stays untouched.

3. Known issues

3.1 Eclipse 3.8 (Remote Linux)

There is a compiler version conflict between the Parallel Task plug-in and a Parallel Task project when using Eclipse 3.8 on the University of Auckland’s Remote Linux. To fix it, set the JRE to JDK 1.8: go to Window → Preferences, then Java → Installed JREs → Add → Standard VM → Next → Directory, locate the JDK 1.8 install (under /usr/lib/jvm/), and enable its checkbox.