Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implement Double Buffer in C

So I have a very high data acquisition rate of 16MB/s. I am reading 4MB of data into a buffer from a device file and then processing it. However, this method of writing then reading was to slow for the project. I would like to implement a double buffer in C.

In order to simplify my idea of the double buffer I decided not to include reading from a device file for simplicity. What I have created is a C program that spawns two separate threads readThread and writeThread. I made readThread call my swap function that swaps the pointers of the buffers.

This implementation is terrible because I am using shared memory outside of the Mutex. I am actually slightly embarrassed to post it, but it will at least give you an idea of what I am trying to do. However, I can not seem to come up with a practical way of reading and writing to separate buffers at the same time then calling a swap once both threads finished writing and reading.

Can someone please tell me if its possible to implement double buffering and give me an idea of how to use signals to control when the threads read and write?

Note that readToBuff (dumb name I know) and writeToBuff aren't actually doing anything at present they have blank functions.

Here is my code:

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

pthread_t writeThread;
pthread_t readThread;
pthread_mutex_t buffer_mutex;

char buff1[4], buff2[4];

struct mutex_shared {
    int stillReading, stillWriting, run_not_over;
    char *writeBuff, *readBuff;
} SHARED;

void *writeToBuff(void *idk) {
    while(!SHARED.run_not_over) {
        SHARED.stillWriting = 1;
        for(int i = 0; i < 4; i++) {
        }
        SHARED.stillWriting = 0;
        while(SHARED.stillReading){};
    }    
    printf("hello from write\n");
    return NULL;
}

void *readToBuff(void *idk) {
    while(!SHARED.run_not_over) {
        SHARED.stillReading = 1;
        for(int i = 0; i < 4; i++) {
        }
        while(SHARED.stillWriting){};
        swap(writeThread,readThread);
    }

    printf("hello from read");
    return NULL;
}

void swap(char **a, char **b){
    pthread_mutex_lock(&buffer_mutex);
        printf("in swap\n");
        char *temp = *a;
        *a = *b;
        *b = temp;
        SHARED.stillReading = 0;
        //SHARED.stillWriting = 0;
    pthread_mutex_unlock(&buffer_mutex);
}

int main() {
    SHARED.writeBuff = buff1;
    SHARED.readBuff = buff2;
    printf("buff1 address %p\n", (void*) &buff1);
    printf("buff2 address %p\n", (void*) &buff2);

    printf("writeBuff address its pointing to %p\n", SHARED.writeBuff);
    printf("readBuff address its pointing to %p\n", SHARED.readBuff);

    swap(&SHARED.writeBuff,&SHARED.readBuff);

    printf("writeBuff address its pointing to %p\n", SHARED.writeBuff);
    printf("readBuff address its pointing to %p\n", SHARED.readBuff);

    pthread_mutex_init(&buffer_mutex,NULL);

    printf("Creating Write Thread\n");

    if (pthread_create(&writeThread, NULL, writeToBuff, NULL)) {

        printf("failed to create thread\n");
        return 1;
    }
    printf("Thread created\n");
    printf("Creating Read Thread\n");
    if(pthread_create(&readThread, NULL, readToBuff, NULL)) {
            printf("failed to create thread\n");
            return 1;
    }
    printf("Thread created\n");
    pthread_join(writeThread, NULL);
    pthread_join(readThread, NULL);
    exit(0);
}
like image 994
Michael Stumpf Avatar asked Oct 17 '22 18:10

Michael Stumpf


1 Answers

Using a pair of semaphores seems like it would be easier. Each thread has it's own semaphore to indicate that a buffer is ready to be read into or written from, and each thread has it's own index into a circular array of structures, each containing a pointer to buffer and buffer size. For double buffering, the circular array only contains two structures.

The initial state sets the read thread's semaphore count to 2, the read index to the first buffer, the write threads semaphore count to 0, and the write index to the first buffer. The write thread is then created which will immediately wait on its semaphore.

The read thread waits for non-zero semaphore count (sem_wait) on its semaphore, reads into a buffer, sets the buffer size, increments the write threads semaphore count (sem_post) and "advances" it's index to the circular array of structures.

The write thread waits for non-zero semaphore count (sem_wait) on its semaphore, writes from a buffer (using the size set by read thread), increments the read threads semaphore count (sem_post) and "advances" it's index to the circular array of structures.

When reading is complete, the read thread sets a structure's buffer size to zero to indicate the end of the read chain, then waits for the write thread to "return" all buffers.

The circular array of structures could include more than just 2 structures, allowing for more nesting of data.

I've had to use something similar for high speed data capture, but in this case, the input stream was faster than a single hard drive, so two hard drives were used, and the output alternated between two write threads. One write thread operated on the "even" buffers, the other on the "odd" buffers.

In the case of Windows, with it's WaitForMultipleObjects() (something that just about every operating system other than Posix has), each thread can use a mutex and a semaphore, along with its own linked list based message queue. The mutex controls queue ownership for queue updates, the semaphore indicates number of items pending on a queue. For retrieving a message, a single atomic WaitForMultipleObjects() waits for a mutex and a non-zero semaphore count, and when both have occurred, decrements the semaphore count and unblocks the thread. A message sender, just needs a WaitForObject() on the mutex to update another threads message queue, then posts (releases) the threads semaphore and releases the mutex. This eliminates any priority issues between threads.

like image 129
rcgldr Avatar answered Oct 21 '22 08:10

rcgldr