Waitgroups in Cilk

#include <cilk/cilk.h>
#include <cilk/cilk_api.h>
#include <iostream>
#include <chrono>
#include <thread>

// This is the function we'll run in every Cilk spawn.
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() {
    // In Cilk, we don't need an explicit WaitGroup.
    // The cilk_sync directive will ensure all spawned tasks are completed.

    // Launch several Cilk spawns
    for (int i = 1; i <= 5; i++) {
        cilk_spawn worker(i);
    }

    // Block until all spawned tasks are finished
    cilk_sync;

    return 0;
}

This Cilk code demonstrates the equivalent functionality of Go’s WaitGroups using Cilk’s built-in synchronization mechanisms. Here’s an explanation of the key differences and concepts:

  1. We use #include <cilk/cilk.h> and #include <cilk/cilk_api.h> to access Cilk’s functionality.

  2. Instead of goroutines, we use Cilk spawns with the cilk_spawn keyword.

  3. The worker function remains largely the same, but we use C++ standard library functions for I/O and sleep operations.

  4. In the main function, we don’t need to create an explicit WaitGroup. Cilk handles synchronization automatically.

  5. We use a for loop to spawn multiple workers, similar to the Go example.

  6. Instead of wg.Wait(), we use cilk_sync to ensure all spawned tasks are completed before the program exits.

  7. Cilk doesn’t have a direct equivalent to Go’s defer statement, so we don’t need to worry about calling a “Done” function.

To compile and run this Cilk program:

$ cilk++ -O3 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, just like in the original example.

Note that Cilk provides a simpler model for parallelism compared to explicit threading or Go’s goroutines. It automatically handles load balancing and work stealing, which can lead to efficient execution on multi-core systems.

For more advanced use cases, Cilk also provides additional synchronization primitives and parallel constructs, but for this simple example, cilk_spawn and cilk_sync are sufficient to demonstrate the concept of waiting for multiple parallel tasks to complete.