Fix typos and improve wording in a comment
[experiments/double-buffering.git] / double-buffering.c
1 /*
2  * double-buffering - an example of double-buffering with pthreads
3  *
4  * Copyright (C) 2013  Antonio Ospite <ospite@studenti.unina.it>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /*
21  * NOTE: the synchronization scheme used in this program assumes that ALL the
22  * buffers produced will be consumed, each exactly ONCE.
23  *
24  * In other cases when buffer skipping or buffer duplication is wanted the
25  * synchronization scheme can change.
26  */
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/types.h>
32 #include <unistd.h>
33 #include <pthread.h>
34 #include <time.h>
35
36 #define BUFFER_SIZE 10
37 #define MAXLEN 1024
38
39 /* 
40  * The time spent by the producer and the consumer thread is quite predictable
41  * in this program, this may not be the case when some communication with
42  * actual hardware influences the production or consumption phase.
43  */
44 #define PRODUCER_DELAY 50000
45 #define CONSUMER_DELAY 20000
46
47 #define NUM_ITERATIONS 23
48
49 struct buffer {
50         int *data;
51         unsigned int size;
52 };
53
54 struct double_buffer_context {
55         struct buffer buffer[2];
56         int back_buffer_index;
57         int front_buffer_is_consumable;
58         pthread_cond_t front_buffer_cond;
59         pthread_mutex_t mutex;
60 };
61
62 #define BACK_BUFFER(ctx) ((ctx)->buffer[(ctx)->back_buffer_index])
63 #define FRONT_BUFFER(ctx) ((ctx)->buffer[(ctx)->back_buffer_index ^ 1])
64 #define SWAP_BUFFERS(ctx) ((ctx)->back_buffer_index ^= 1)
65
66 static void do_log(char *message)
67 {
68         struct timespec now;
69         double timestamp;
70
71         clock_gettime(CLOCK_MONOTONIC, &now);
72         timestamp = now.tv_sec + now.tv_nsec / 1000000000.0;
73         fprintf(stderr, "[%f] %s\n", timestamp, message);
74 }
75
76 /* the consumer thread will only read from the front buffer */
77 static void *consumer_cb(void *arg)
78 {
79         struct double_buffer_context *ctx = (struct double_buffer_context *) arg;
80         unsigned int i;
81
82         char message[MAXLEN];
83         int len;
84
85         while(1) {
86                 pthread_mutex_lock(&ctx->mutex);
87                 while (!ctx->front_buffer_is_consumable) {
88                         pthread_cond_wait(&ctx->front_buffer_cond, &ctx->mutex);
89                 }
90                 pthread_mutex_unlock(&ctx->mutex);
91
92                 /* consume front buffer */
93                 len = snprintf(message, MAXLEN, "Consuming value: ");
94                 for (i = 0; i < FRONT_BUFFER(ctx).size; i++) {
95                         len += snprintf(message + len, MAXLEN - len, "%d ", FRONT_BUFFER(ctx).data[i]);
96                 }
97                 usleep(CONSUMER_DELAY);
98                 do_log(message);
99
100                 pthread_mutex_lock(&ctx->mutex);
101                 ctx->front_buffer_is_consumable = 0;
102                 pthread_cond_signal(&ctx->front_buffer_cond);
103                 pthread_mutex_unlock(&ctx->mutex);
104         }
105
106         pthread_exit(0);
107         return (void *) 0;
108 }
109
110 int main(void)
111 {
112         int ret;
113         pthread_t consumer_tid;
114         struct double_buffer_context *ctx;
115
116         unsigned int iterations;
117         unsigned int i;
118
119         char message[MAXLEN];
120         int len;
121
122         ctx = malloc(sizeof(*ctx));
123         if (ctx == NULL) {
124                 perror("malloc");
125                 exit(EXIT_FAILURE);
126         }
127         memset(ctx, 0, sizeof(*ctx));
128
129         ctx->buffer[0].data = malloc(BUFFER_SIZE * sizeof(*ctx->buffer[0].data));
130         ctx->buffer[0].size = BUFFER_SIZE;
131         ctx->buffer[1].data = malloc(BUFFER_SIZE * sizeof(*ctx->buffer[1].data));
132         ctx->buffer[1].size = BUFFER_SIZE;
133
134         ctx->back_buffer_index = 0;
135         ctx->front_buffer_is_consumable = 0;
136         pthread_mutex_init(&ctx->mutex, NULL);
137         pthread_cond_init(&ctx->front_buffer_cond, NULL);
138
139         ret = pthread_create(&consumer_tid, NULL, consumer_cb, (void *)ctx);
140         if (ret != 0)
141         {
142                 fprintf(stderr, "can't create thread: %s\n", strerror(ret));
143                 exit(1);
144         }
145
146         /* fixed seed, always the same sequence */
147         srand(0xa02);
148         iterations = NUM_ITERATIONS;
149         while (iterations--) {
150                 /* produce: in the back buffer */
151                 len = snprintf(message, MAXLEN, "Producing value: ");
152                 for (i = 0; i < BACK_BUFFER(ctx).size; i++) {
153                         BACK_BUFFER(ctx).data[i] = rand();
154                         len += snprintf(message + len, MAXLEN - len, "%d ", BACK_BUFFER(ctx).data[i]);
155                 }
156                 usleep(PRODUCER_DELAY);
157                 do_log(message);
158
159                 pthread_mutex_lock(&ctx->mutex);
160                 while (ctx->front_buffer_is_consumable) {
161                         pthread_cond_wait(&ctx->front_buffer_cond, &ctx->mutex);
162                 }
163
164                 SWAP_BUFFERS(ctx);
165
166                 /* signal that the front buffer is ready to be consumed */
167                 ctx->front_buffer_is_consumable = 1;
168                 pthread_cond_signal(&ctx->front_buffer_cond);
169
170                 pthread_mutex_unlock(&ctx->mutex);
171         }
172
173         /* wait for the last consumer run before terminating the thread */
174         pthread_mutex_lock(&ctx->mutex);
175         while (ctx->front_buffer_is_consumable) {
176                 pthread_cond_wait(&ctx->front_buffer_cond, &ctx->mutex);
177         }
178         pthread_mutex_unlock(&ctx->mutex);
179
180         pthread_cancel(consumer_tid);
181         pthread_join(consumer_tid, NULL);
182
183         free(ctx->buffer[1].data);
184         free(ctx->buffer[0].data);
185         free(ctx);
186
187         exit(0);
188 }