Waitgroups in C

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

#define NUM_WORKERS 5

// This structure will be used to pass data to our threads
typedef struct {
    int id;
    pthread_mutex_t* mutex;
    int* counter;
} thread_data;

// This is the function we'll run in every thread
void* worker(void* arg) {
    thread_data* data = (thread_data*)arg;
    
    printf("Worker %d starting\n", data->id);
    
    // Sleep to simulate an expensive task
    sleep(1);
    
    printf("Worker %d done\n", data->id);
    
    // Decrement the counter when the thread is done
    pthread_mutex_lock(data->mutex);
    (*data->counter)--;
    pthread_mutex_unlock(data->mutex);
    
    pthread_exit(NULL);
}

int main() {
    pthread_t threads[NUM_WORKERS];
    thread_data thread_data_array[NUM_WORKERS];
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    int counter = NUM_WORKERS;

    // Launch several threads and initialize their data
    for (int i = 0; i < NUM_WORKERS; i++) {
        thread_data_array[i].id = i + 1;
        thread_data_array[i].mutex = &mutex;
        thread_data_array[i].counter = &counter;
        
        int rc = pthread_create(&threads[i], NULL, worker, (void*)&thread_data_array[i]);
        if (rc) {
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
    }

    // Wait for all threads to complete
    while (1) {
        pthread_mutex_lock(&mutex);
        if (counter == 0) {
            pthread_mutex_unlock(&mutex);
            break;
        }
        pthread_mutex_unlock(&mutex);
        usleep(100000);  // Sleep for 100ms to avoid busy waiting
    }

    pthread_mutex_destroy(&mutex);
    return 0;
}

This C program demonstrates the concept of waiting for multiple threads to finish, which is similar to the WaitGroup concept in the original example. Here’s an explanation of the key parts:

  1. We define a thread_data structure to pass information to each thread, including an ID, a mutex, and a pointer to a shared counter.

  2. The worker function simulates work by sleeping for one second, then prints a message when it’s done.

  3. In main, we create an array of pthread_t to hold our threads, and an array of thread_data to hold the data for each thread.

  4. We use a mutex and a counter to keep track of how many threads are still running. This replaces the WaitGroup from the original example.

  5. We create NUM_WORKERS threads, each running the worker function with its own thread_data.

  6. After creating all threads, we enter a loop that checks the counter. When the counter reaches zero, all threads have finished, and we can exit the program.

  7. We use usleep to avoid busy waiting while checking the counter.

To compile and run this program, you would typically do:

$ gcc -o waitgroups waitgroups.c -pthread
$ ./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.

Note that this approach doesn’t provide a straightforward way to propagate errors from workers. For more advanced use cases in C, you might need to implement additional error handling mechanisms or use more sophisticated threading libraries.