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;
}
source to share
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
.
source to share
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 beint
, notchar
. Otherwise, itc
may never compare withEOF
, because it isEOF
not necessarily in the rangechar
(so itfgetc()
returnsint
).c
can also be local tofunctionRead()
. -
There is no need for a signal
condRead
in the main thread because the reader will checkcondition
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.
source to share