Waitgroups in CLIPS

Our example demonstrates how to wait for multiple threads to finish using Java’s CountDownLatch. This is similar to the concept of WaitGroups in other languages.

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class CountDownLatchExample {

    // This is the method we'll run in every thread.
    private static void worker(int id, CountDownLatch latch) {
        try {
            System.out.printf("Worker %d starting%n", id);
            // Sleep to simulate an expensive task.
            TimeUnit.SECONDS.sleep(1);
            System.out.printf("Worker %d done%n", id);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // Signal that this worker is done.
            latch.countDown();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // This CountDownLatch is used to wait for all the
        // threads launched here to finish.
        CountDownLatch latch = new CountDownLatch(5);

        // Create a fixed thread pool
        ExecutorService executor = Executors.newFixedThreadPool(5);

        // Launch several threads and decrement the CountDownLatch
        // counter for each.
        for (int i = 1; i <= 5; i++) {
            final int workerId = i;
            executor.execute(() -> worker(workerId, latch));
        }

        // Block until the CountDownLatch counter goes back to 0;
        // all the workers notified they're done.
        latch.await();

        // Shutdown the executor
        executor.shutdown();

        // Note that this approach has no straightforward way
        // to propagate exceptions from workers. For more
        // advanced use cases, consider using CompletableFuture
        // or other concurrency utilities from java.util.concurrent.
    }
}

To run the program:

$ javac CountDownLatchExample.java
$ java CountDownLatchExample
Worker 1 starting
Worker 2 starting
Worker 3 starting
Worker 4 starting
Worker 5 starting
Worker 2 done
Worker 1 done
Worker 3 done
Worker 5 done
Worker 4 done

The order of workers starting up and finishing is likely to be different for each invocation.

In this Java example, we use a CountDownLatch instead of a WaitGroup. The CountDownLatch serves a similar purpose, allowing us to wait for a set of operations to complete.

We also use an ExecutorService to manage our thread pool, which is a more idiomatic way to handle concurrent tasks in Java. Each worker is submitted as a task to the executor.

The worker method is now a static method that takes a CountDownLatch as a parameter. It calls countDown() on the latch when it’s done, which is equivalent to calling Done() on a WaitGroup.

In the main method, we initialize the CountDownLatch with a count of 5, create an ExecutorService, and then submit 5 worker tasks. We then call await() on the latch, which blocks until all workers have completed.

Finally, we shut down the ExecutorService. This is an important step in Java to properly clean up thread resources.

Note that Java’s concurrency utilities offer more advanced features for handling exceptions and results from concurrent tasks, such as CompletableFuture or the ExecutorCompletionService.