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:
We use
#include <cilk/cilk.h>
and#include <cilk/cilk_api.h>
to access Cilk’s functionality.Instead of goroutines, we use Cilk spawns with the
cilk_spawn
keyword.The
worker
function remains largely the same, but we use C++ standard library functions for I/O and sleep operations.In the main function, we don’t need to create an explicit WaitGroup. Cilk handles synchronization automatically.
We use a for loop to spawn multiple workers, similar to the Go example.
Instead of
wg.Wait()
, we usecilk_sync
to ensure all spawned tasks are completed before the program exits.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.