Closing Channels in C

Our example demonstrates how to close channels and handle channel closure in C. Since C doesn’t have built-in channels or goroutines, we’ll use POSIX threads and a custom implementation of channels to simulate similar behavior.

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

#define CHANNEL_SIZE 5

typedef struct {
    int* buffer;
    int head;
    int tail;
    int count;
    int size;
    pthread_mutex_t mutex;
    pthread_cond_t not_full;
    pthread_cond_t not_empty;
    bool is_closed;
} channel_t;

void channel_init(channel_t* ch, int size) {
    ch->buffer = malloc(sizeof(int) * size);
    ch->head = 0;
    ch->tail = 0;
    ch->count = 0;
    ch->size = size;
    ch->is_closed = false;
    pthread_mutex_init(&ch->mutex, NULL);
    pthread_cond_init(&ch->not_full, NULL);
    pthread_cond_init(&ch->not_empty, NULL);
}

void channel_close(channel_t* ch) {
    pthread_mutex_lock(&ch->mutex);
    ch->is_closed = true;
    pthread_cond_broadcast(&ch->not_empty);
    pthread_mutex_unlock(&ch->mutex);
}

bool channel_send(channel_t* ch, int value) {
    pthread_mutex_lock(&ch->mutex);
    while (ch->count == ch->size && !ch->is_closed) {
        pthread_cond_wait(&ch->not_full, &ch->mutex);
    }
    if (ch->is_closed) {
        pthread_mutex_unlock(&ch->mutex);
        return false;
    }
    ch->buffer[ch->tail] = value;
    ch->tail = (ch->tail + 1) % ch->size;
    ch->count++;
    pthread_cond_signal(&ch->not_empty);
    pthread_mutex_unlock(&ch->mutex);
    return true;
}

bool channel_receive(channel_t* ch, int* value) {
    pthread_mutex_lock(&ch->mutex);
    while (ch->count == 0 && !ch->is_closed) {
        pthread_cond_wait(&ch->not_empty, &ch->mutex);
    }
    if (ch->count == 0 && ch->is_closed) {
        pthread_mutex_unlock(&ch->mutex);
        return false;
    }
    *value = ch->buffer[ch->head];
    ch->head = (ch->head + 1) % ch->size;
    ch->count--;
    pthread_cond_signal(&ch->not_full);
    pthread_mutex_unlock(&ch->mutex);
    return true;
}

void* worker(void* arg) {
    channel_t* jobs = (channel_t*)arg;
    int job;
    bool more;
    while (1) {
        more = channel_receive(jobs, &job);
        if (more) {
            printf("received job %d\n", job);
        } else {
            printf("received all jobs\n");
            return NULL;
        }
    }
}

int main() {
    channel_t jobs;
    channel_init(&jobs, CHANNEL_SIZE);

    pthread_t worker_thread;
    pthread_create(&worker_thread, NULL, worker, &jobs);

    for (int j = 1; j <= 3; j++) {
        channel_send(&jobs, j);
        printf("sent job %d\n", j);
    }
    channel_close(&jobs);
    printf("sent all jobs\n");

    pthread_join(worker_thread, NULL);

    int value;
    bool ok = channel_receive(&jobs, &value);
    printf("received more jobs: %s\n", ok ? "true" : "false");

    return 0;
}

This C program simulates the behavior of the original example using POSIX threads and a custom channel implementation. Here’s a breakdown of the changes:

  1. We define a channel_t struct to represent our channel, along with functions to initialize, close, send to, and receive from the channel.

  2. The worker function runs in a separate thread and receives jobs from the channel until it’s closed.

  3. In the main function, we create a channel, start a worker thread, send jobs to the channel, close it, and wait for the worker to finish.

  4. After the worker is done, we try to receive from the closed channel to demonstrate that it returns false.

To compile and run this program, you would use:

$ gcc -o closing_channels closing_channels.c -lpthread
$ ./closing_channels

The output should be similar to the original example, demonstrating the closing of channels and how the worker responds to this closure.

Note that this C implementation is more complex due to the lack of built-in concurrency primitives. In practice, you might use a more robust threading library or a different concurrency model in C, depending on your specific requirements.