In .net the AggregateException class allows you to throw an exception containing multiple exceptions.
For example, you would want to throw an AggregateException if you ran multiple tasks in parallel and some of them failed with exceptions.
Does java have an equivalent class?
The specific case I want to use it in:
public static void runMultipleThenJoin(Runnable... jobs) {
final List<Exception> errors = new Vector<Exception>();
try {
//create exception-handling thread jobs for each job
List<Thread> threads = new ArrayList<Thread>();
for (final Runnable job : jobs)
threads.add(new Thread(new Runnable() {public void run() {
try {
job.run();
} catch (Exception ex) {
errors.add(ex);
}
}}));
//start all
for (Thread t : threads)
t.start();
//join all
for (Thread t : threads)
t.join();
} catch (InterruptedException ex) {
//no way to recover from this situation
throw new RuntimeException(ex);
}
if (errors.size() > 0)
throw new AggregateException(errors);
}
Java 7's Throwable.addSuppressed(Throwable) will do something similar, although it was built for a slightly different purpose (try-with-resource)
I'm not aware of any built-in or library classes, as I've never even though of wanting to do this before (typically you would just chain the exceptions), but it wouldn't be that hard to write yourself.
You'd probably want to pick one of the Exceptions to be "primary" so it can be used to fill in stacktraces, etc.
public class AggregateException extends Exception {
private final Exception[] secondaryExceptions;
public AggregateException(String message, Exception primary, Exception... others) {
super(message, primary);
this.secondaryExceptions = others == null ? new Exception[0] : others;
}
public Throwable[] getAllExceptions() {
int start = 0;
int size = secondaryExceptions.length;
final Throwable primary = getCause();
if (primary != null) {
start = 1;
size++;
}
Throwable[] all = new Exception[size];
if (primary != null) {
all[0] = primary;
}
Arrays.fill(all, start, all.length, secondaryExceptions);
return all;
}
}
You can represent multiple taska as
List<Callable<T>> tasks
Then if you want the computer to actually do them in parallel use
ExecutorService executorService = .. initialize executor Service
List<Future<T>> results = executorService.invokeAll ( ) ;
Now you can iterate through the results.
try
{
T val = result . get ( ) ;
}
catch ( InterruptedException cause )
{
// this is not the exception you are looking for
}
catch ( ExecutionExeception cause )
{
Throwable realCause = cause . getCause ( ) // this is the exception you are looking for
}
So realCause (if it exists) is whatever exception what thrown in its associated task.
I don't really see why you should use exceptions in the first place to mark tasks as incomplete/failed but in any case, it shouldn't be hard to create one yourself. Got any code to share so that we could help you with a more specific answer?
Related
I am trying to stop a long running method after 10 seconds of execution, so far i followed the timer instructions on baeldung.
https://www.baeldung.com/java-stop-execution-after-certain-time#1-using-a-timer
When the method is a simple call to a thread sleep it works, but when I call my function with sub methods it doesn't stop.
My implementation looks like this:
class TimeOutTask extends TimerTask {
private Thread t;
private Timer timer;
TimeOutTask(Thread t, Timer timer){
this.t = t;
this.timer = timer;
}
public void run() {
if (t != null && t.isAlive()) {
t.interrupt();
timer.cancel();
}
}
}
class Execution implements Runnable {
private String carpeta;
private Experiment exp;
public Execution(String carpeta, Experiment exp) {
this.carpeta = carpeta;
this.exp = exp;
}
#Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
exp.executeExperiment(carpeta);
}
} catch (InterruptedException e) {
System.out.println("Fin de ejecución por tiempo");
}
}
}
And the way I am calling this execution is throught the executeTimedExperiment method
public Experiment() {
this.cases = new ArrayList<>();
}
private void executeTimedExperiment(String carpeta){
Thread t = new Thread(new Execution(carpeta,this));
Timer timer = new Timer();
timer.schedule(new TimeOutTask(t, timer), 10000);
t.start();
}
private void executeExperiment(String carpeta) throws InterruptedException {
String[] files = getFiles(carpeta);
Arrays.sort(files);
for (String file : files) {
executeCase(carpeta, file);
}
}
private boolean executeCase(String carpeta, String file) {
Graph g = readDataToGraph(carpeta + "/" + file);
Solution s = new ExactSolutionGenerator().ExactSolution(g);
addNewCase(file, s);
}
The executeExperiment method is the long running and I marked it with InterruptedException but the compiler tells me the exception is never throw.
What happens now when I execute it is that it runs normally without stoppping.
I am not sure if I need to add InterruptedException to the submethods or something else, but I would like to not touch the submethods if possible.
Thanks in advance.
You will need to do more than add throws InterruptedException to all of those ‘submethods’ (and your own methods). The body of each of those methods must be altered to properly respond to interrupts.
It is not possible to arbitrarily stop running code. Interrupts are cooperative—they only mean something if the thread being interrupted pays attention to them.
Your run() method does this properly: by placing the entire loop inside a try/catch, any InterruptedException will cause the loop to terminate and thus the thread will terminate.
But the methods it calls must do the same thing. Your run method calls executeExperiment, which does this:
String[] files = getFiles(carpeta);
I don’t know how long that method takes, but if it takes any significant amount of time at all (more than a fraction of a second), it needs to be capable of throwing InterruptedException in the middle of the file reading.
executeExperiment also calls executeCase, which calls the ‘submethods’ readDataToGraph, ExactSolution, and addNewCase. As above, each of those methods which takes more than a fraction of a second needs to respond to an interrupt by throw InterruptedException. So, I’m afraid you will need to modify them.
An example would be:
private Graph readDataToGraph(String filename)
throws InterruptedException {
Graph graph = new Graph();
try (BufferedReader reader = Files.newBufferedReader(Path.of(filename))) {
String line;
while ((line = reader.readLine()) != null) {
graph.addData(convertDataToGraphEntry(line));
if (Thread.interrupted()) {
throw new InterruptedException();
}
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
Compiler tells you the exception is never throw is beacuse your executeExperiment method is uninterruptable(Unlike some blocking methods, e.g. Object#wait), so thread.interrupt does not make the thread executing this method receive an InterruptedException.
Maybe you need to check whether the current thread has been interrupted every time you iterate files in your executeExperiment method, if it is, then throw an InterruptedException.(But this may still be inaccurate, because the executeCase method may be executed for a long time.)
My multi-threaded class is supposed to carry out three operations – operation1, operation2, and operation3 – on a number of objects of the class ClassA, where each type of operation is dependant on the earlier operation. For this, I have tried to implement the producer-consumer pattern using a number of BlockingQueues and an ExecutorService.
final ExecutorService executor = ForkJoinPool.commonPool();
final BlockingQueue<ClassA> operationOneQueue = new ArrayBlockingQueue<>(NO_OF_CLASS_A_OBJECTS);
final BlockingQueue<ClassA> operationTwoQueue = new ArrayBlockingQueue<>(NO_OF_CLASS_A_OBJECTS);
final BlockingQueue<ClassA> operationThreeQueue = new ArrayBlockingQueue<>(NO_OF_CLASS_A_OBJECTS);
final BlockingQueue<ClassA> resultQueue = new ArrayBlockingQueue<>(NO_OF_CLASS_A_OBJECTS);
The operations are implemented like this:
void doOperationOne() throws InterruptedException {
ClassA objectA = operationOneQueue.take();
objectA.operationOne();
operationTwoQueue.put(objectA);
}
where each type of operation has its own corresponding method, with its "own" in-queue and out-queue. Each operation method calls the appropriate method on the ClassA object. The method doOperationThree puts ClassA objects in the resultQueue, meaning they have been completely processed.
First, I fill the operationOneQueue with all ClassA objects that are to be operated on. Then, I try to assign executable tasks to the ExecutorService like this:
while (resultQueue.size() < NO_OF_CLASS_A_OBJECTS) {
executor.execute(() -> {
try {
doOperationOne();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
executor.execute(() -> {
try {
doOperationTwo();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
executor.execute(() -> {
try {
doOperationThree();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
executor.shutdown();
Running my program, I get a java.util.concurrent.RejectedExecutionException.
Operation1: ClassA object 0
Operation2: ClassA object 0
Operation1: ClassA object 1
Operation3: ClassA object 0
....
Operation1: ClassA object 46
Operation2: ClassA object 45
Operation3: ClassA object 45
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Queue capacity exceeded
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.growArray(ForkJoinPool.java:912)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.lockedPush(ForkJoinPool.java:867)
at java.base/java.util.concurrent.ForkJoinPool.externalPush(ForkJoinPool.java:1911)
at java.base/java.util.concurrent.ForkJoinPool.externalSubmit(ForkJoinPool.java:1930)
at java.base/java.util.concurrent.ForkJoinPool.execute(ForkJoinPool.java:2462)
at concurrent.operations.Program1.main(Program1.java:96)
What am I doing wrong? How can I achieve this without over-saturating the thread pool?
Edit: Full disclosure – this is homework with some requirements. 1. I must use ForkJoinPool.commonPool() and must not set the number of threads myself, 2. I must use the consumer-producer pattern, and 3. I must not modify ClassA.
I really like doing concurrent stuff, so I did try writing it. I did use CompletableFuture which a) does run in the ForkJoinPool.commonPool by default and b) makes the actual processing really easy:
while (true) {
final ClassA nextOperation = queue.take();
CompletableFuture.runAsync(nextOperation::operationOne)
.thenRun(nextOperation::operationTwo)
.thenRun(nextOperation::operationThree)
.thenRun(() -> resultQueue.add(nextOperation));
}
This will take ClassA objects from the queue and execute all their operations concurrently, but in order.
You did leave out where the tasks are coming from, and whether you need the consumer to terminate. Generally you don't want to, and it does make matters a bit more complicated.
private static final int COUNT = 10;
private static final Random RANDOM = new Random();
public static void main(String[] args) throws ExecutionException, InterruptedException {
BlockingQueue<ClassA> runnables = new ArrayBlockingQueue<>(COUNT);
BlockingQueue<ClassA> finished = new ArrayBlockingQueue<>(COUNT);
// start producer
ExecutorService createTaskExecutor = Executors.newSingleThreadExecutor();
createTaskExecutor.submit(() -> fillQueue(runnables));
// wait for all consumer tasks to finish
while (finished.size() != COUNT) {
try {
// we need to poll instead of waiting forever
// because the last tasks might still be running
// while there are no others to add anymore
// so we need to check again if all have finished in the meantime
final ClassA nextOperation = runnables.poll(2, TimeUnit.SECONDS);
if (nextOperation != null) {
CompletableFuture.runAsync(nextOperation::operationOne)
.thenRun(nextOperation::operationTwo)
.thenRun(nextOperation::operationThree)
.thenRun(() -> finished.add(nextOperation));
}
} catch (InterruptedException e) {
System.err.println("exception while retrieving next operation");
// we will actually need to terminate now, or probably never will
throw e;
}
}
System.out.printf("finished tasks (%d):%n", finished.size());
for (ClassA classA : finished) {
System.out.printf("finished task %d%n", classA.designator);
}
createTaskExecutor.shutdown();
}
private static void fillQueue(BlockingQueue<ClassA> runnables) {
// start thread filling the queue at random
for (int i = 0; i < COUNT; i++) {
runnables.add(new ClassA(i));
try {
Thread.sleep(RANDOM.nextInt(1_000));
} catch (InterruptedException e) {
System.err.println("failed to add runnable");
}
}
}
Since you didn't provide ClassA, I used this one. It contains an identifier so you can track which is running at what time.
class ClassA {
private static final Random RANDOM = new Random();
public final int designator;
public ClassA(int i) {
designator = i;
}
public void operationOne() {
System.out.printf("%d: operation 1%n", designator);
sleep();
}
public void operationTwo() {
System.out.printf("%d: operation 2%n", designator);
sleep();
}
public void operationThree() {
System.out.printf("%d: operation 3%n", designator);
sleep();
}
private static void sleep() {
try {
Thread.sleep(RANDOM.nextInt(5_000));
} catch (InterruptedException e) {
System.err.println("interrupted while executing task");
}
}
}
I'm testing how CompletableFuture works. I am interested in how to execute tasks in parallel:
try {
CompletableFuture one = CompletableFuture.runAsync(() -> {
throw new RuntimeException("error");
});
CompletableFuture two = CompletableFuture.runAsync(() -> System.out.println("2"));
CompletableFuture three = CompletableFuture.runAsync(() -> System.out.println("3"));
CompletableFuture all = CompletableFuture.allOf(one, two, three);
all.get();
} catch (InterruptedException e) {
System.out.println(e);
} catch (ExecutionException e) {
System.out.println(e);
}
In this case they will be executed all.
1. Is it possible to interrupt all running threads when threre is an exception in one of them?
2. When this code is inside a class' method which can be invoked from different threads will it be threadsafe?
1.Is it possible to interrupt all running threads when there is an exception in one of them?
Yes, it is possible. All threads should have an access to common object which state can be changed and read by other threads. It can be for example AtomicInteger. See below example:
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
public class Dates {
public static void main(String[] args) throws Exception {
try {
AtomicInteger excCounter = new AtomicInteger(0);
CompletableFuture one = CompletableFuture.runAsync(new ExcRunnable(excCounter));
CompletableFuture two = CompletableFuture.runAsync(new PrintRunnable("2", excCounter));
CompletableFuture three = CompletableFuture.runAsync(new PrintRunnable("3", excCounter));
CompletableFuture all = CompletableFuture.allOf(one, two, three);
all.get();
} catch (InterruptedException | ExecutionException e) {
System.out.println(e);
}
}
}
class ExcRunnable implements Runnable {
private final AtomicInteger excCounter;
public ExcRunnable(AtomicInteger excCounter) {
this.excCounter = excCounter;
}
#Override
public void run() {
Random random = new Random();
int millis = (int) (random.nextDouble() * 5000);
System.out.println("Wait " + millis);
Threads.sleep(450);
// Inform another threads that exc occurred
excCounter.incrementAndGet();
throw new RuntimeException("error");
}
}
class PrintRunnable implements Runnable {
private final String name;
private final AtomicInteger excCounter;
public PrintRunnable(String name, AtomicInteger excCounter) {
this.name = name;
this.excCounter = excCounter;
}
#Override
public void run() {
int counter = 10;
while (counter-- > 0 && excCounter.get() == 0) {
System.out.println(name);
Threads.sleep(450);
}
}
}
class Threads {
static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
We have 3 tasks: two which prints it's name and one which throws exception after some time. Before exception is thrown counter is incremented to inform other tasks that one of them failed and they should finish executions. Print jobs are checking this counter and in case condition is not met they finish it's job. When you comment excCounter.incrementAndGet(); line other tasks finish theirs job without knowing that one of them thrown exception.
When this code is inside a class' method which can be invoked from different threads will it be thread safe?
Take a look on the definition of thread safety. For example, assume that print tasks increment common counter with every printed line. If counter is primitive int it is not thread safety because counter value could be replaced. But if you use AtomicInteger it is thread safety because AtomicInteger is thread safety.
1
if any of your async task throw exception, all.get() will throw exception.
That means, you can cancel all CF in catch clause.
But, your async tasks need to be interrupt friendly i.e. check for interrupt flag periodic or handle InterruptedException and return early.
Task cancellation should always be handled using interrupt mechanism
2
All the reference variables mentioned by you are local, so there is no need to worry about thread safety. Local variables are always thread safe.
I would like to execute 3 methods at same time in Java (obviously I need threads), and that I would like to execute not in separate class and not in my main method, but in my custom method. Can it be done?
I have find this piece of code - Execute Multiple Methods Simaltaneously Using Thread In Java
and reused best marked answer for my example, with parameters that I have:
private void fetchData() {
boolean t1_run = true;
boolean t2_run = true;
boolean t3_run = true;
int SLEEP_TIME = 100;//Configurable.
Thread thread1 = new Thread() {
public void run() {
while (t1_run)
{
try
{
subjects = new BeanItemContainer<KltSubject>(KltSubject.class, clijentService.getSubjecteByType(Integer.valueOf(creditor)));
Thread.sleep(SLEEP_TIME);//So that other thread also get the chance to execute.
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
};
Thread thread2 = new Thread() {
public void run() {
while (t2_run)
{
try
{
programs = new BeanItemContainer<Program>(Program.class, creditService.getAllPrograms());
Thread.sleep(SLEEP_TIME);//So that other thread also get the chance to execute.
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
};
Thread thread3 = new Thread() {
public void run() {
while (t3_run)
{
try
{
credits = new BeanItemContainer<CreditExt>(CreditExt.class, creditService.getAllCredits());
Thread.sleep(SLEEP_TIME);//So that other thread also get the chance to execute.
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
};
thread1.start();
thread2.start();
thread3.start();
}
Now, before I've put my variables in threads (variables named: subjects, programs and credits), I could easily get their values (these variables you can see in above code in this example in their run methods, but are defined outside my fetchData() method and are visible).
After setting code like this, and executing it, I recieve null pointer exception because obviously varaibles are not seen any more after threads are executed. How to get that values after execution in threads?
P.S. can this code be written more elegantly, with less lines of code? If Java 8 (or Java 7) can done it - show me please how?
Use advanced Threading API : ExecutorService invokeAll()
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException
Executes the given tasks, returning a list of Futures holding their status and results when all complete. Future.isDone() is true for each element of the returned list. Note that a completed task could have terminated either normally or by throwing an exception. The results of this method are undefined if the given collection is modified while this operation is in progress.
Sample code:
ExecutorService service = Executors.newFixedThreadPool(3);
List<MyCallable> futureList = new ArrayList<MyCallable>();
MyCallable1 myCallable1 = new MyCallable1(); // your first thread
MyCallable2 myCallable2 = new MyCallable2(); // your second thread
MyCallable3 myCallable1 = new MyCallable3(); // your third thread
futureList.add(myCallable1);
futureList.add(myCallable2);
futureList.add(myCallable3);
System.out.println("Start");
try{
List<Future<Long>> futures = service.invokeAll(futureList);
for(Future<Long> future : futures){
try{
System.out.println("future.isDone = " + future.isDone());
System.out.println("future: call ="+future.get());
}
catch(Exception err1){
err1.printStackTrace();
}
}
}catch(Exception err){
err.printStackTrace();
}
service.shutdown();
You can have a look into few more examples in this article
You can use join() to wait for the threads to finish.
eg:
thread1.join();
thread2.join();
thread3.join();
when all the threads are done, the variables shouldn't be null.
Making this more compact depends on what exactly you're trying to do. Here is an example:
private List<Result> fetchData() {
final List<Result> results = new ArrayList<Result>();
List<Thread> threads = new ArrayList<Thread>();
for(int i = 0; i < 3; i++){
Thread t = new Thread() {
public void run() {
Result result = getResult();
synchronized(results) {
results.add(result);
}
}
};
t.start();
threads.add(t);
}
for(Thread t:threads) {
t.join();
}
return results;
}
You can simply use a CompletableFuture:
CompletableFuture<BeanItemContainer<KltSubject>> job1 = CompletableFuture.supplyAsync(() ->
new BeanItemContainer<>(KltSubject.class, clijentService.getSubjecteByType(creditor)));
CompletableFuture<BeanItemContainer<Program>> job2 = CompletableFuture.supplyAsync(() ->
new BeanItemContainer<>(Program.class, creditService.getAllPrograms()));
CompletableFuture<BeanItemContainer<CreditExt>> job3 = CompletableFuture.supplyAsync(() ->
new BeanItemContainer<>(CreditExt.class, creditService.getAllCredits()));
subjects = job1.join();
programs = job2.join();
credits = job3.join();
The method supplyAsync will initiate the asynchronous computation of a value and join will return the computed value, waiting, if necessary. But if all three actions imply querying the same database, it might be possible, that you don’t gain any performance advantage as the database may be the limiting factor.
Forgive me if the title is a bit vague. I will try to explain a bit better what i am trying to accomplish.
There is a function called parsebytes that is part of an external interface that i have implemented. It takes an array of bytes and a length. All parsing in this particular program runs on a single thread so i want to get my data out of parsebytes as quickly as possible so it can return to getting more data off the line. My methodology in pseudocode is this:
Create an externally running thread (ParserThreadClass).
every time parsebytes is called, put the bytes into a queue in the ParserThreadClass by looping through all the bytes and doing a byteQueue.add(bytes[i]). This code is surrounded by a synchronized(byteQueue)
That, in effect, should free the parsebytes to go back and get more data.
While that is happening, my ParserThreadClass is also running. This is the code in the run() function
while (!shutdown) //while the thread is still running
{
synchronized (byteQueue)
{
bytes.addAll(byteQueue); //an arraylist
byteQueue.clear();
}
parseMessage(); //this will take the bytes arraylist and build an xml message.
}
Am i being overly inefficient here? If so, can someone give me an idea of how i should tackle this?
This is how I've tried to solve the problem previously. Basically you have a producer thread, like you have here, that reads the file and puts items onto the queue. Then you have a worker thread that reads things from the queue and processes them. Code is below, but it looks essentially the same as what you're doing. What I found is that this gives me just about no speed up, because the processing I need to do per line is pretty quick, relative to the disk read. If the parsing you have to do is pretty intensive, or the chunks are pretty large, you could see some speed up doing it this way. But if it's pretty minimal, don't expect to see much in the way of performance improvement, because the process is IO bound. In those situations, you need to parallelize the disk access, which you can't really do on a single machine.
public static LinkedBlockingQueue<Pair<String, String>> mappings;
public static final Pair<String, String> end =
new Pair<String, String>("END", "END");
public static AtomicBoolean done;
public static NpToEntityMapping mapping;
public static Set<String> attested_nps;
public static Set<Entity> possible_entities;
public static class ProducerThread implements Runnable {
private File f;
public ProducerThread(File f) {
this.f = f;
}
public void run() {
try {
BufferedReader reader = new BufferedReader(new FileReader(f));
String line;
while ((line = reader.readLine()) != null) {
String entities = reader.readLine();
String np = line.trim();
mappings.put(new Pair<String, String>(np, entities));
}
reader.close();
for (int i=0; i<num_threads; i++) {
mappings.put(end);
}
} catch (InterruptedException e) {
System.out.println("Producer thread interrupted");
} catch (IOException e) {
System.out.println("Producer thread threw IOException");
}
}
}
public static class WorkerThread implements Runnable {
private Dictionary dict;
private EntityFactory factory;
public WorkerThread(Dictionary dict, EntityFactory factory) {
this.dict = dict;
this.factory = factory;
}
public void run() {
try {
while (!done.get()) {
Pair<String, String> np_ent = mappings.take();
if (np_ent == end) {
done.set(false);
continue;
}
String entities = np_ent.getRight();
String np = np_ent.getLeft().toLowerCase();
if (attested_nps == null || attested_nps.contains(np)) {
int np_index = dict.getIndex(np);
HashSet<Entity> entity_set = new HashSet<Entity>();
for (String entity : entities.split(", ")) {
Entity e = factory.createEntity(entity.trim());
if (possible_entities != null) {
possible_entities.add(e);
}
entity_set.add(e);
}
mapping.put(np_index, entity_set);
}
}
} catch (InterruptedException e) {
System.out.println("Worker thread interrupted");
}
}
}
EDIT:
Here's code for the main thread that starts the producer and worker threads:
Thread producer = new Thread(new ProducerThread(f), "Producer");
producer.start();
ArrayList<Thread> workers = new ArrayList<Thread>();
for (int i=0; i<num_threads; i++) {
workers.add(new Thread(new WorkerThread(dict, factory), "Worker"));
}
for (Thread t : workers) {
t.start();
}
try {
producer.join();
for (Thread t : workers) {
t.join();
}
} catch (InterruptedException e) {
System.out.println("Main thread interrupted...");
}
It should also be fine to have the work done in the producer thread just be done in the main thread, taking out the need to start and join with another thread in the main code. Be sure to start the worker threads before going through the file, though, and join with them after you've done the work. I'm not sure about the performance differences between that way and the way I have here, though.