Waitgroups in C++

Our program demonstrates how to wait for multiple threads to finish using a C++ implementation similar to Go’s WaitGroup.

#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
#include <mutex>
#include <condition_variable>

class WaitGroup {
private:
    std::mutex mutex;
    std::condition_variable cv;
    int count = 0;

public:
    void add(int n) {
        std::unique_lock<std::mutex> lock(mutex);
        count += n;
    }

    void done() {
        std::unique_lock<std::mutex> lock(mutex);
        if (--count == 0) {
            cv.notify_all();
        }
    }

    void wait() {
        std::unique_lock<std::mutex> lock(mutex);
        cv.wait(lock, [this] { return count == 0; });
    }
};

// This is the function we'll run in every thread.
void worker(int id) {
    std::cout << "Worker " << id << " starting\n";

    // Sleep to simulate an expensive task.
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Worker " << id << " done\n";
}

int main() {
    // This WaitGroup is used to wait for all the
    // threads launched here to finish.
    WaitGroup wg;

    // Launch several threads and increment the WaitGroup
    // counter for each.
    for (int i = 1; i <= 5; i++) {
        wg.add(1);

        // Wrap the worker call in a lambda that makes sure to tell
        // the WaitGroup that this worker is done. This way the worker
        // itself does not have to be aware of the concurrency primitives
        // involved in its execution.
        std::thread([&wg, i]() {
            worker(i);
            wg.done();
        }).detach();
    }

    // Block until the WaitGroup counter goes back to 0;
    // all the workers notified they're done.
    wg.wait();

    // Note that this approach has no straightforward way
    // to propagate errors from workers. For more
    // advanced use cases, consider using other synchronization
    // primitives or libraries that support error handling.
}

To compile and run the program:

$ g++ -std=c++11 -pthread waitgroups.cpp -o waitgroups
$ ./waitgroups
Worker 1 starting
Worker 2 starting
Worker 3 starting
Worker 4 starting
Worker 5 starting
Worker 1 done
Worker 2 done
Worker 3 done
Worker 4 done
Worker 5 done

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

In this C++ version, we’ve created a WaitGroup class that mimics the functionality of Go’s sync.WaitGroup. The worker function remains largely the same, but we use std::thread to create our threads instead of goroutines.

The main function structure is similar, but we use C++ lambda functions to wrap our worker calls. We also use detach() on our threads to allow them to run independently.

Note that C++ doesn’t have built-in goroutines or a direct equivalent to Go’s WaitGroup, so we’ve implemented a simple version using standard C++ threading primitives. For more complex scenarios, you might want to consider using more robust threading libraries or C++20’s coroutines for a closer approximation of Go’s concurrency model.