Tricky Deadlock when programming with threads in C

I tried to create a file copier using streams and somehow the program is blocking when entering functions. I've searched a lot and I've tried a lot, but I just can't seem to find a solution. I would be glad if someone could help me!

//gcc -o threadcopyfile threadcopyfile.c -lpthread -lrt
#include <stdio.h>
#include <pthread.h> 
#include <unistd.h> 
#include <string.h>
#include <stdlib.h>


pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //init mutex
pthread_cond_t condRead = PTHREAD_COND_INITIALIZER; //init cond. variables
pthread_cond_t condWrite = PTHREAD_COND_INITIALIZER; //init cond. variables

int n, x = 1, i = 0, j, condition = 0;
char c;

pthread_t pRead;
pthread_t pWrite;

char ringpuffer[10000];

void *functionRead(void *argv){
    char **args = (char **) argv; //Give args the arguments from argv
    FILE* fp;
    if ((fp=fopen(args[1], "r")) == NULL){ //Open the inputfile, check if failed
        perror("fopen\n"); 
        exit(EXIT_FAILURE);
    }
    n = atoi(args[3]);
    printf("Entered functionRead\n");
    do{
        pthread_mutex_lock(&mutex); //Lock mutex
        while(condition == 1){
            pthread_cond_wait(&condRead, &mutex); //Wait for cond_signal
        }
        i = 0;
        for(i = 0; i<=n; i++){ //Put chars into the ringbuffer
            c = fgetc(fp);
            ringpuffer[i] = c;
            i++;
        }
        x++;
        printf("Hit!");
        condition = 1;
        pthread_mutex_unlock(&mutex); //Unlock mutex
        pthread_cond_signal(&condWrite); //Send signal to CondWrite
    }while ((c=fgetc(fp)) != EOF);

    if ((fclose(fp)) == EOF){ //Close the file
        perror("fclose\n"); 
        exit(EXIT_FAILURE);
    }
    return 0;
}

void *functionWrite(void *argv){
    char **args = (char **) argv;   //Give args the arguments from argv
    FILE* fp;
    if ((fp=fopen(args[2], "w")) == NULL){ //Open the outputfile, check if allowed
        perror("fopen\n"); 
        exit(EXIT_FAILURE);
    }
    n = atoi(args[3]);
    printf("Entered functionWrite\n");
    for (j = 0; j < x; j++){
        pthread_mutex_lock(&mutex); //Lock mutex
        while(condition == 0){
            pthread_cond_wait(&condWrite, &mutex); //Wait for singel from condRead
        }
        fwrite(ringpuffer, 1, n, fp); //Write to file
        printf("Hit too!");
        condition = 0;
        pthread_mutex_unlock(&mutex); //Unlock
        pthread_cond_signal(&condRead); //Send signal again to condRead
    }

    if ((fclose(fp)) == EOF){
        perror("fclose\n"); 
        exit(EXIT_FAILURE);
    }
    return EXIT_SUCCESS;
}

int main(int argc, char **argv)
{
    if (argc != 4){
        printf("Only 3 parameters allowed!\n");
        exit(EXIT_FAILURE);
    }
    printf("File to Copy: %s\nTarget: %s\nHow many chars: %s\n", argv[1], argv[2], argv[3]);

    pthread_create(&pWrite, NULL, &functionWrite, ((void *)argv)); //Create thread, threadid in pWrite, execute functionWrite, and give argv as arguments
    pthread_create(&pRead, NULL, &functionRead, ((void *)argv)); //Create thread, threadid in pRead, execute functionRead and give argv as arguments 

    printf("Threads created, file copy in progress\n");
    pthread_cond_signal(&condRead); //Unblocks the thread

    pthread_join(pRead, NULL);
    pthread_join(pWrite, NULL);
    printf("done.\n");
    return 0;
}

      

+3


source to share


2 answers


You cannot use one mutex with two such condition variables. If a thread calls pthread_cond_signal()

and blocks a mutex before the second thread wakes up, the second thread can never return from pthread_cond_wait()

, as it cannot lock the mutexes.



You also do not set condition = 1

as your functionRead()

and functionWrite()

there condition = 0

.

+2


source


Your main concern is the termination condition for your email flow. By the time the read thread decided to exit, it had already increased x

, so the write thread would wait one more time for it ... forever.

You also shouldn't call c = fgetc(fp)

again in the terminating state of the while loop, because you end up throwing away the character that was read there. Instead, your loop that reads the buffer could be:

i = 0;
while (i < n && (c = fgetc(fp)) != EOF) { //Put chars into the ringbuffer
    ringpuffer[i] = c;
    i++;
}

      

At the end of this cycle, we know that:

  • We read exactly i

    characters in the buffer; and
  • if i < n

    , we have reached the end of the file and should exit.

You can use the second of these points to know when the read stream should exit, so the general loop looks like this:

do {
    pthread_mutex_lock(&mutex); //Lock mutex
    while(condition == 1){
        pthread_cond_wait(&condRead, &mutex); //Wait for cond_signal
    }
    i = 0;
    while (i < n && (c = fgetc(fp)) != EOF) { //Put chars into the ringbuffer
        ringpuffer[i] = c;
        i++;
    }
    printf("Hit!");
    condition = 1;
    pthread_mutex_unlock(&mutex); //Unlock mutex
    pthread_cond_signal(&condWrite); //Send signal to CondWrite
} while (i == n);

      



To fix the actual problem with the completion of the recording thread, you can use this same termination condition. However, we need to hold the lock while testing i

on the write stream because it could otherwise participate in the write and write i

to the reader stream. To do this, we move the unlock and lock outside of the loop (it doesn't matter, because the only significant length of time a thread does not hold the lock is while in pthread_cond_wait()

). We also use i

as the number of bytes to write from the buffer, because where the reader's stream leaves this value:

pthread_mutex_lock(&mutex); //Lock mutex
printf("Entered functionWrite\n");
do {
    while(condition == 0){
        pthread_cond_wait(&condWrite, &mutex); //Wait for singel from condRead
    }
    fwrite(ringpuffer, 1, i, fp); //Write to file
    printf("Hit too!");
    condition = 0;
    pthread_cond_signal(&condRead); //Send signal again to condRead
} while (i == n);
pthread_mutex_unlock(&mutex); //Unlock

      

After these changes j

and are x

no longer needed.

You have a couple more problems I see:

  • n

    set by both the writer and the reader thread without blocking. You can simply set it to the reader thread, as the write thread does not access it until the read thread is executed once.

  • The variable c

    should be int

    , not char

    . Otherwise, it c

    may never compare with EOF

    , because it is EOF

    not necessarily in the range char

    (so it fgetc()

    returns int

    ). c

    can also be local to functionRead()

    .

  • There is no need for a signal condRead

    in the main thread because the reader will check condition

    before waiting.

But perhaps most fundamentally, I think you may have missed this exercise, because although you have an array named ringpuffer

, you are not actually using it as a ring buffer. You don't get any parallelism - your design means your threads are executing in blocking mode. I suspect the idea is that you should have two separate flags - buffer_empty

and buffer_full

- rather than one "read or write" condition. Your file reader works until buffer_full

it is true, and your file writer works until buffer_empty

it is true, so in the fraction of the time that both flags are false, you can start both streams.

0


source







All Articles