/* * double-buffering - an example of double-buffering with pthreads * * Copyright (C) 2013 Antonio Ospite * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* * NOTE: the synchronization scheme used in this program assumes that ALL the * buffers produced will be consumed, each exactly ONCE. * * In other cases when buffer skipping or buffer duplication is wanted the * synchronization scheme can change. */ #include #include #include #include #include #include #include #define BUFFER_SIZE 10 #define MAXLEN 1024 /* * The time spent by the producer and the consumer thread is quite predictable * in this program, this may not be the case when some communication with * actual hardware influences the production or consumption phase. */ #define PRODUCER_DELAY 50000 #define CONSUMER_DELAY 20000 #define NUM_ITERATIONS 23 struct buffer { int *data; unsigned int size; }; struct double_buffer_context { struct buffer buffer[2]; int back_buffer_index; int front_buffer_is_consumable; pthread_cond_t front_buffer_cond; pthread_mutex_t mutex; }; #define BACK_BUFFER(ctx) ((ctx)->buffer[(ctx)->back_buffer_index]) #define FRONT_BUFFER(ctx) ((ctx)->buffer[(ctx)->back_buffer_index ^ 1]) #define SWAP_BUFFERS(ctx) ((ctx)->back_buffer_index ^= 1) static void do_log(char *message) { struct timespec now; double timestamp; clock_gettime(CLOCK_MONOTONIC, &now); timestamp = now.tv_sec + now.tv_nsec / 1000000000.0; fprintf(stderr, "[%f] %s\n", timestamp, message); } /* the consumer thread will only read from the front buffer */ static void *consumer_cb(void *arg) { struct double_buffer_context *ctx = (struct double_buffer_context *) arg; unsigned int i; char message[MAXLEN]; int len; while(1) { pthread_mutex_lock(&ctx->mutex); while (!ctx->front_buffer_is_consumable) { pthread_cond_wait(&ctx->front_buffer_cond, &ctx->mutex); } pthread_mutex_unlock(&ctx->mutex); /* consume front buffer */ len = snprintf(message, MAXLEN, "Consuming value: "); for (i = 0; i < FRONT_BUFFER(ctx).size; i++) { len += snprintf(message + len, MAXLEN - len, "%d ", FRONT_BUFFER(ctx).data[i]); } usleep(CONSUMER_DELAY); do_log(message); pthread_mutex_lock(&ctx->mutex); ctx->front_buffer_is_consumable = 0; pthread_cond_signal(&ctx->front_buffer_cond); pthread_mutex_unlock(&ctx->mutex); } pthread_exit(0); return (void *) 0; } int main(void) { int ret; pthread_t consumer_tid; struct double_buffer_context *ctx; unsigned int iterations; unsigned int i; char message[MAXLEN]; int len; ctx = malloc(sizeof(*ctx)); if (ctx == NULL) { perror("malloc"); exit(EXIT_FAILURE); } memset(ctx, 0, sizeof(*ctx)); ctx->buffer[0].data = malloc(BUFFER_SIZE * sizeof(*ctx->buffer[0].data)); ctx->buffer[0].size = BUFFER_SIZE; ctx->buffer[1].data = malloc(BUFFER_SIZE * sizeof(*ctx->buffer[1].data)); ctx->buffer[1].size = BUFFER_SIZE; ctx->back_buffer_index = 0; ctx->front_buffer_is_consumable = 0; pthread_mutex_init(&ctx->mutex, NULL); pthread_cond_init(&ctx->front_buffer_cond, NULL); ret = pthread_create(&consumer_tid, NULL, consumer_cb, (void *)ctx); if (ret != 0) { fprintf(stderr, "can't create thread: %s\n", strerror(ret)); exit(1); } /* fixed seed, always the same sequence */ srand(0xa02); iterations = NUM_ITERATIONS; while (iterations--) { /* produce: in the back buffer */ len = snprintf(message, MAXLEN, "Producing value: "); for (i = 0; i < BACK_BUFFER(ctx).size; i++) { BACK_BUFFER(ctx).data[i] = rand(); len += snprintf(message + len, MAXLEN - len, "%d ", BACK_BUFFER(ctx).data[i]); } usleep(PRODUCER_DELAY); do_log(message); pthread_mutex_lock(&ctx->mutex); while (ctx->front_buffer_is_consumable) { pthread_cond_wait(&ctx->front_buffer_cond, &ctx->mutex); } SWAP_BUFFERS(ctx); /* signal that the front buffer is ready to be consumed */ ctx->front_buffer_is_consumable = 1; pthread_cond_signal(&ctx->front_buffer_cond); pthread_mutex_unlock(&ctx->mutex); } /* wait for the last consumer run before terminating the thread */ pthread_mutex_lock(&ctx->mutex); while (ctx->front_buffer_is_consumable) { pthread_cond_wait(&ctx->front_buffer_cond, &ctx->mutex); } pthread_mutex_unlock(&ctx->mutex); pthread_cancel(consumer_tid); pthread_join(consumer_tid, NULL); free(ctx->buffer[1].data); free(ctx->buffer[0].data); free(ctx); exit(0); }