Search code examples
cpthreadspipefile-descriptor

how to make read() in thread block on a pipe's file descriptor?


I'm experimenting on how to communicate between a thread and the main function in C There is a behavior that I don't understand in the following code :

#include <pthread.h>

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

void* output(void* pipe1);

int main(int argc, const char *argv[])
{
    pthread_t   tid0;
    int         pipe1[2];
    char        buffer[200];

// Creating the pipe
    pipe(pipe1);
// Creating the thread and passing the pipe as argument
    pthread_create(&tid0, NULL, output, &pipe1);
// Input from user
    scanf("%s", buffer);
// Writing to the pipe
    write(pipe1[1], buffer, strlen(buffer));
    return 0;
}

void* output(void* pipe1) {
     char buffer[200];

// Reading the pipe and print the buffer
     read(((int*)pipe1)[0], buffer, strlen(buffer));
     printf("thread say: %s\n", buffer);
     pthread_exit(NULL);
}

Why the read function doesn't block on the pipe's file descriptor ?

Maybe I should close the end of the pipe but since they share the same memory space, the error "bad file descriptor" is returned when I will call read or write.

Maybe you can guide me to other methods if pipe is really a bad solution (with an example it will be amazing ! :) )

Many thanks !

EDIT: SOLUTION

Many thank for your answer here is the code that have the expected behavior

#include <pthread.h>

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

void* output(void* pipe1);

int main(int argc, const char *argv[])
{
    pthread_t   tid0;
    int         pipe1[2];
    char        buffer[200];

// Creating the pipe
    pipe(pipe1);
// Creating the thread and passing the pipe as argument
    pthread_create(&tid0, NULL, output, &pipe1);

// Input from user
    scanf("%s", buffer);
// Writing to the pipe
    if (write(pipe1[1], buffer, strlen(buffer)) < 0) {
        perror("write");
        exit(1);
    }
    // join so the main "wait" for the thread
    pthread_join(tid0, NULL);
    return 0;
}


void* output(void* pipe1) {
    char        buffer[200];
    int         nread;

// Reading the pipe and print the buffer
    nread = read(((int*)pipe1)[0], buffer, sizeof buffer - 1);
    if (nread < 0) {
        fprintf(stderr, "ERROR\n");
        perror("read");
        exit(1);
    }
    buffer[nread] = '\0';
    fprintf(stderr, "thread say: %s\n", buffer);
    pthread_exit(NULL);
}

Solution

  • char buffer[200];
    read(((int*)pipe1)[0], buffer, strlen(buffer));
    

    You are calling strlen on an uninitialized buffer. This is allowed to crash your program. Instead, you got lucky, and all it did was tell read to read zero bytes, so read returned without doing anything.

    What you actually want is

    ssize_t nread = read(((int *)pipe1)[0], buffer, sizeof buffer - 1);
    if (nread < 0) {
        perror("read");
        return 0;
    }
    buffer[nread] = '\0';
    

    What read wants to be told is how much space you are giving it to read into, not the length of any string that may or may not already be in that space. That's sizeof buffer, minus one so we always have space to add a string terminator.

    It's correct to use strlen when writing, because you only want to write the actual string, not any junk that might be beyond the end of the string; but then write doesn't write the string terminator to the pipe, so read doesn't read one, so you have to add it by hand. And, of course, always check for errors.

    Also, keep in mind that the threads run simultaneously. Even after fixing this bug, the write may already have happened by the time the reader-thread calls read, and if it hasn't, it probably will happen very soon. If you want to observe the reader-thread actually blocking in read you need to delay before calling write.