Stateful Goroutines in C

Our example demonstrates how to manage state using threads and message passing in C. This approach aligns with the idea of sharing memory by communicating and having each piece of data owned by exactly one thread.

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

#define NUM_READ_THREADS 100
#define NUM_WRITE_THREADS 10

typedef struct {
    int key;
    int value;
} State;

typedef struct {
    int key;
    int* resp;
} ReadOp;

typedef struct {
    int key;
    int val;
    int* resp;
} WriteOp;

State state[5];
pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER;

atomic_uint_fast64_t readOps = 0;
atomic_uint_fast64_t writeOps = 0;

void* state_manager(void* arg) {
    while (1) {
        pthread_mutex_lock(&state_mutex);
        // Simulating channel select with a random choice
        if (rand() % 2 == 0) {
            // Simulate read operation
            int key = rand() % 5;
            state[key].value = rand() % 100;
        } else {
            // Simulate write operation
            int key = rand() % 5;
            state[key].value = rand() % 100;
        }
        pthread_mutex_unlock(&state_mutex);
        usleep(1000);  // Sleep for 1ms
    }
    return NULL;
}

void* read_routine(void* arg) {
    while (1) {
        ReadOp read = {rand() % 5, NULL};
        pthread_mutex_lock(&state_mutex);
        read.resp = &state[read.key].value;
        pthread_mutex_unlock(&state_mutex);
        atomic_fetch_add(&readOps, 1);
        usleep(1000);  // Sleep for 1ms
    }
    return NULL;
}

void* write_routine(void* arg) {
    while (1) {
        WriteOp write = {rand() % 5, rand() % 100, NULL};
        pthread_mutex_lock(&state_mutex);
        state[write.key].value = write.val;
        write.resp = &state[write.key].value;
        pthread_mutex_unlock(&state_mutex);
        atomic_fetch_add(&writeOps, 1);
        usleep(1000);  // Sleep for 1ms
    }
    return NULL;
}

int main() {
    srand(time(NULL));

    pthread_t state_thread;
    pthread_create(&state_thread, NULL, state_manager, NULL);

    pthread_t read_threads[NUM_READ_THREADS];
    for (int r = 0; r < NUM_READ_THREADS; r++) {
        pthread_create(&read_threads[r], NULL, read_routine, NULL);
    }

    pthread_t write_threads[NUM_WRITE_THREADS];
    for (int w = 0; w < NUM_WRITE_THREADS; w++) {
        pthread_create(&write_threads[w], NULL, write_routine, NULL);
    }

    sleep(1);  // Let the threads work for a second

    uint64_t readOpsFinal = atomic_load(&readOps);
    printf("readOps: %lu\n", readOpsFinal);
    uint64_t writeOpsFinal = atomic_load(&writeOps);
    printf("writeOps: %lu\n", writeOpsFinal);

    return 0;
}

In this C version, we use POSIX threads (pthreads) to simulate the concurrent behavior of goroutines. The state_manager function acts as the goroutine that owns the state. Instead of channels, we use a mutex to synchronize access to the shared state.

We create separate threads for read and write operations. Each thread performs its respective operation in a loop, simulating the behavior of the Go example.

To count operations, we use atomic operations from C11’s <stdatomic.h> header, which provides thread-safe counters.

Note that this C implementation is a simplified approximation of the Go example. C doesn’t have built-in support for channels or select statements, so we’ve had to use alternative synchronization primitives and techniques.

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

$ gcc -o stateful_threads stateful_threads.c -lpthread
$ ./stateful_threads
readOps: 71708
writeOps: 7177

The output shows the number of read and write operations completed in one second, which should be similar to the Go version.

This C implementation demonstrates how to manage state using threads and mutexes. While it’s more complex than the Go version due to the lack of high-level concurrency primitives in C, it achieves a similar result. The choice between this approach and other synchronization methods would depend on the specific requirements of your program and your familiarity with different concurrency patterns in C.