CS 245 Lab 12 – Threaded Image Processing
First, if you have checkpoints left over from last lab, get them inspected during the first 15 minutes of this lab.
Don’t forget to work in pairs! Where possible, please work with someone that you did not work with last week. The exchange of new ideas and perspectives is not an opportunity you want to rob yourself of.
Synopsis
In this lab, you will look at breaking up a task into smaller subtasks that are completed concurrently. Instead of processing all pixels with just one CPU, you will assign to each processing unit a portion of the pixels. The CPUs run independently of one another. If conditions are right, your task may be completed in a fraction of the time than it would with just one CPU.
Each chunk of work runs in a separate thread. The Java library provides a Thread class that has two important methods: start and join. Method start() schedules the thread to be run on a processor. The code that started the thread can wait for it to finish by calling join(). To create the chunk of work, we write the code that does the work inside a class that implements the Runnable interface and pass an instance of this worker class to the Thread constructor.
An example application that demonstrates all of these points follows. Each of three threads sleeps for a random amount of time, but they all do so at the same time. Instead of the main thread waiting a.time + b.time + c.time seconds, it just waits max(a.time, b.time, c.time) seconds. Slick, huh?
package lab12;
public class ThreadEcho {
public static void main(String[] args) {
Thread a = new Thread(new Echoer(0));
Thread b = new Thread(new Echoer(1));
Thread c = new Thread(new Echoer(2));
// Start the threads going concurrently.
a.start();
b.start();
c.start();
// Wait for them all to finish.
try {
a.join();
b.join();
c.join();
} catch (InterruptedException e) {
}
System.out.println("All threads have finished.");
}
private static class Echoer implements Runnable {
private int threadIndex;
public Echoer(int threadIndex) {
this.threadIndex = threadIndex;
}
@Override
public void run() {
int nMillis = (int) (Math.random() * 1000 * 20);
System.out.println("Thread " + threadIndex + " started and will sleep for " + nMillis / 1000.0 + " seconds.");
try {
Thread.sleep(nMillis);
} catch (InterruptedException e) {
// Sleep ended prematurely. Oh, well.
}
System.out.println("Thread " + threadIndex + " finished.");
}
}
}
Test this code out before moving on. You may use it as a model for your own code.
Checkpoint 1
Person A types.
Write a class that implements the Runnable interface. Have its constructor accept parameters for two BufferedImages (one to read from, the other to write to), an int thread index, and an int thread count.
In its run method, process a portion of the pixels in the input image and write the results to the corresponding pixels in the output image. What processing means is really up to you. You could lighten the image, negate the colors, swap a color channel, detect edges, and so on. Start with something simple, because the image processing isn’t the focus of this lab.
What portion of the image should a given thread process? This is left unspecified, but keep these two points in mind:
- Each thread processes a different set of pixels in the input image. Concurrency works best when the concurrent tasks are independent.
- When all threads have finished, all pixels in the input image will have been processed.
Checkpoint 2
Person B types.
Write a main method that implements the following algorithm:
read in input image
make identically-sized output image
make list of threads
for i up to nThreads
add new thread to list
start thread processing
nJoined = 0
while nJoined < nThreads
join on thread from list
if join successful
++nJoined
write out output image
If you have time, you might be interested in timing the processing task with different numbers of threads.