简体   繁体   English

使用C语言编程时使用Tricky Deadlock

[英]Tricky Deadlock while programming with Threads in C

I tried to make a file copier using threads and somehow, the programm locks when entering the functions. 我尝试使用线程制作文件复制器,不知何故,程序在进入函数时锁定。 I searched a lot and I tried many things, but I simply can't find the solution. 我搜索了很多,我尝试了很多东西,但我根本找不到解决方案。 I would be glad if somebody 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;
}

You can't use a single mutex with two condition variables like that. 您不能使用具有两个条件变量的单个互斥锁。 If a thread calls pthread_cond_signal() and relocks the mutex before the second thread wakes up, the second thread will never be able to return from pthread_cond_wait() since it can't lock the mutex. 如果一个线程调用pthread_cond_signal()并在第二个线程唤醒之前重新锁定互斥锁,则第二个线程将永远无法从pthread_cond_wait()返回,因为它无法锁定互斥锁。

You also don't seem to be setting condition = 1 , as both your functionRead() and functionWrite() have condition = 0 in them. 你似乎也没有设置condition = 1 ,因为你的functionRead()functionWrite()都有condition = 0

Your main issue is the termination condition for your writer thread - by the time the reader thread has decided to exit, it has already incremented x so the writer thread will wait another time for it... forever. 你的主要问题是你的编写器线程的终止条件 - 当读者线程决定退出时,它已经增加了x所以编写器线程将等待另一次...永远。

You also shouldn't be calling c = fgetc(fp) again in the termination condition of the while loop, because you end up throwing away the character that was read there. 你也不应该在while循环的终止条件下再次调用c = fgetc(fp) ,因为你最终丢弃了那里读取的字符。 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 loop we know that: 在这个循环结束时,我们知道:

  • We read exactly i characters into the buffer; 我们准确地将i字符读入缓冲区; and
  • if i < n , we have reached end-of-file and should exit. 如果i < n ,我们已经到达文件结尾并且应该退出。

You can use the second of these points to know when the reader thread should finish, so the overall loop looks like: 您可以使用这些点中的第二个来了解读取器线程何时应该完成,因此整个循环看起来像:

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 writer thread terminating, you can use this same termination condition. 要解决写入程序线程终止的实际问题,可以使用相同的终止条件。 However we need to keep the lock held while we test i in the writer thread, because otherwise it could race with a write to i in the reader thread. 但是,当我们在编写器线程中测试i ,我们需要保持锁定,否则它可能会在读取器线程中对i进行写入。 To do this, we move the unlock and lock outside the loop (this doesn't matter, because the only substantial lenght of time that the thread isn't holding the lock is while it is in pthread_cond_wait() anyway). 为此,我们将解锁和锁定移到循环外部(这没关系,因为线程没有持有锁的唯一时间长度是在pthread_cond_wait() )。 We also use i as the number of bytes to write from the buffer, because that's where the reader thread leaves that value: 我们还使用i作为从缓冲区写入的字节数,因为这是读者线程离开该值的位置:

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 x are no longer needed. 在这些更改之后,不再需要jx

You have a couple of other issues that I can see: 您还可以看到其他几个问题:

  • n is set by both the writer and the reader thread, without a lock held. n由编写器和读取器线程设置,没有锁定。 You can just set it in the reader thread, since the writer thread doesn't access it until the reader thread has executed once. 您可以在读者线程中设置它,因为在读者线程执行一次之前,编写器线程不会访问它。

  • The variable c should be an int , not a char . 变量c应该是int ,而不是char Otherwise c might never compare equal to EOF , because EOF is not necessarily in the range of char (that's why fgetc() returns int ). 否则c可能永远不会比较等于EOF ,因为EOF不一定在char范围内(这就是fgetc()返回int的原因)。 c can also just be local to functionRead() . c也可以是functionRead()本地。

  • There's no need to signal condRead in the main thread, because the reader will test condition before waiting. 没有必要在主线程中发出condRead信号,因为读者会在等待之前测试condition

But perhaps most fundamentally, I think you may have missed the point of this exercise, because although you have an array called ringpuffer you are not actually using it as a ring buffer. 但也许最根本的是,我认为你可能已经错过了这个练习的重点,因为虽然你有一个名为ringpuffer的数组,但实际上并没有将它用作环形缓冲区。 You aren't getting any parallelism - your design means that your threads run in lock-step. 您没有获得任何并行性 - 您的设计意味着您的线程以锁定步骤运行。 I suspect that the idea is that you should have two separate flags - buffer_empty and buffer_full - rather than a single "reading or writing" condition. 我怀疑这个想法是你应该有两个单独的标志 - buffer_emptybuffer_full - 而不是一个“读或写”条件。 Your file reader runs as long as buffer_full isn't true, and your file writer runs as long as buffer_empty isn't true, so for the portion of time that both flags are false you can have both threads running. 只要buffer_full不为true,您的文件读取器就会运行,并且只要buffer_empty不为true,您的文件buffer_empty就会运行,因此对于两个标志都为false的部分时间,您可以让两个线程都运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM