0

I'm trying to gain a better understanding of fork() and concurrency in c programming. I'm a novice, and I'm having trouble understanding the logic. I tried to make a simple producer-consumer program using fork(). basically, a producer() function should take a character from stdin, and write it to a file. At the same time, a second process runs the consumer code, which is supposed to read the last character in the file and echo it to the screen. The producer() and consumer() functions are working fine by themselves, i.e. they are doing what each are supposed to do, but the problem is in the concurrency. Here is my code:

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

FILE* fp;
//char c;

void producer(){
        char c=' ';
        while(c!='x'){
                puts("enter a char");
                c = getchar();

                while((fp = fopen("shared.txt", "at"))==NULL); //while the file is in use by another program

                fputc(c,fp);

                if(c!='\n')puts("file written to successfully");
                fclose(fp);
        }
        return;
}

char readChar(){
        char c;
        while((fp = fopen("shared.txt", "rt"))==NULL);
        fseek(fp, -1, SEEK_END);
        c = fgetc(fp);
        fclose(fp);
        return c;
}

void consumer(){
        char c;
        do{
                c = readChar();
                printf("This is the latest character supplied: %c\n", c);
        }while(c!='x');

}

int main(){
        int pid = fork(); //now we fork processes

        if(pid ==0 ){
                producer();  //the child process should run and create some text in the file
        }else{
                wait(); consumer(); 
        }
}

I tried to add wait statements after the calls to producer() and consumer() in their respective branches, but basically no matter what, the program fails to do what I want . if in main () I have

int main(){
        int pid = fork(); //now we fork processes

        if(pid ==0 ){
                producer();   //the child process should run and create some text in the file
        }else{
                consumer(); 
        }
}

I get stuck in an infinite loop. Adding wait(); after the function call in one or both branches doesn't help, because the infinite loop occurs before control passes to either wait().

If I try this:

int main(){
        int pid = fork(); //now we fork processes

        if(pid ==0 ){
                producer();   //the child process should run and create some text in the file
        }else{
               wait(); consumer(); 
        }
}

I can enter text from the stdin until I enter 'x', but then as expected the consumer reads only the last character written to the file.

Is there a way to get this to work with wait statements?

mch
  • 9,424
  • 2
  • 28
  • 42
P. Gillich
  • 289
  • 1
  • 9
  • 1
    OT: regarding: `char c=' ';` and `c = getchar();` The function: `getchar()` returns an `int`, not a `char`. – user3629249 Apr 30 '19 at 15:01
  • regarding: `int pid = fork();` and related references to `pid` 1) the type should be: `pid_t`, not `int`. 2) the function: `fork()` has three kinds of returns: 1) <0 means an error occurred. 2) ==0 means in the child process 3) >0 means in the parent process. The code should be checking for all three conditions – user3629249 Apr 30 '19 at 15:05
  • 1
    what is `wait();` ? where it comes from ? Of course the consumer runs as fast as possible, it never blocks contrarily to the producer waiting for an input. `if(c!='\n')puts("file written to successfully");` is **very** strange, did you supposed to check the result of _fputc_ ? – bruno Apr 30 '19 at 15:05
  • regarding: `while((fp = fopen("shared.txt", "rt"))==NULL);` How many times do you want the code to open the same file? once is plenty. Should be checking for errors. Suggest: `fp = fopen( "shared.txt", "rt" ); If( !fp ) { perror( "fopen for reading -shared.txt- failed" ); exit( EXIT_FAILURE ); }` – user3629249 Apr 30 '19 at 15:09
  • @user3629249 probably it is better to usleep/sleep a little if _fopen_ fails because that means the producer did not yet created the file – bruno Apr 30 '19 at 15:10
  • "_Is there a way to get this to work with wait statements?_" what kind of wait you expect / waiting on what ? the end of the producer ? – bruno Apr 30 '19 at 15:12
  • the child process should be terminated via a call to `exit()`, not via a `return;` – user3629249 Apr 30 '19 at 15:12
  • the `wait()` will 'pause' until the child process terminates before calling the `consumer()` function, so there is no need for the consumer() function to contain a call to `sleep()` – user3629249 Apr 30 '19 at 15:14
  • @bruno, The consumer() function will not be executed until the child process has exited, so no need to have the `consumer()` function sleep – user3629249 Apr 30 '19 at 15:16
  • regarding: `wait()`: Please read the MAN page – user3629249 Apr 30 '19 at 15:17
  • @user3629249 not sure this is what the OP wants, may be OP wants programs running in concurrency the consumer being a kind of `tail -f` ? The question is unclear, and unfortunately the OP does not answer to the remarks so we lost all our time ... – bruno Apr 30 '19 at 15:17
  • OT: regarding: `c = fgetc(fp);` and `char c;` The function: `fgetc()` returns an `int`, not a `char` – user3629249 Apr 30 '19 at 15:22
  • 1
    @bruno, I agree, for the desired operation, a 'named pipe' or 'fifo' would be a much better implementation over using a regular file – user3629249 Apr 30 '19 at 15:30

1 Answers1

0

the problem is in the concurrency

I'd say the problem is in the (lack of) synchronization. In a producer / consumer arrangement, the producer usually has a means to signal the consumer that a new item is available for consumption, and the consumer waits for that signal before trying to consume one. Details vary from there, but they typically also include a way for the producer to signal the consumer that no more items will be forthcoming.

Your consumer does not wait for any explicit signal, and it does not use the data available to it (file length) to determine that a new item is available. On the other hand, it makes no effort to keep its own place among the items consumed, so it can easily miss items if the producer gets ahead of it. Moreover, the consumer busy-loops, performing expensive I/O operations no less, which is a very costly way to go about this.

Is there a way to get this to work with wait statements?

Only if you wanted the producer to run to completion before the consumer consumed anything. That's what wait() does: it waits for another process to terminate. In that case, you would want the consumer to just read the file from the beginning, character by character, instead of leaping straight to the end.

If you want producer and consumer to make progress concurrently, then the easiest way to go about it would be to make use of the facilities already provided to you by the system, by using a FIFO or a pipe instead of a regular file. Then the producer could just write character after character and the consumer could just read character after character, without any of this reopening and repositioning nonsense.

If you must do it with a regular file, then you could use a pair of semaphores or a mutex + condition variable to make producer and consumer take turns. Alternatively, there are various ways that the consumer could monitor the file to detect when it has changed (stat / fstat, inotify, etc.) to avoid needlessly trying to read from it, and you could combine that with it tracking its own position in that file, so as not to re-read data it has already consumed. Ideally, neither program would open the file more than once, but the producer might need to fflush after each write.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157