0

In the code below I create 8 threads and each of those prints a string and its id. However, I don't see any printf output in stdout from PrintHello function. One strange thing that is happening is that if run the main using debugger (CLion) printf indeed outputs the intended output. I actually suspect that no code inside PrintHello function is run, not just printf. What could be causing this?

Also according to this answer printf is thread-safe so it shouldn't be an issue of different threads competing for stdout.

This is the code (it as adapted from these slides):

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

#define BAD_EXIT 1
#define NUM_TASKS 8

char * messages[NUM_TASKS];

void * PrintHello(void * taskIdPtr) {
    int taskId;
    sleep(1);
    taskId = *((int *) taskIdPtr);
    printf("Task id = %d, message = %s\n", taskId, messages[taskId]);
    free(taskIdPtr);
    pthread_exit(NULL);
}
int main(int argc, char ** argv) {
    pthread_t threads[NUM_TASKS];
    int * taskIdPtr;
    int rc, t;
    messages[0] = "English: Hello World!";
    messages[1] = "French: Bonjour, le monde!";
    messages[2] = "Spanish: Hola al mundo";
    messages[3] = "Klingon: Nuq neH!";
    messages[4] = "German: Guten Tag, Welt!";
    messages[5] = "Russian: Zdravstvytye, mir!";
    messages[6] = "Japan: Sekai e konnichiwa!";
    messages[7] = "Latin: Orbis, te saluto!";

    for(t = 0; t < NUM_TASKS; t++) {
        taskIdPtr = (int *) malloc(sizeof(int));
        *taskIdPtr = t;
        printf("Creating thread %d\n", t);
        rc = pthread_create(&threads[t], NULL, PrintHello, (void *) taskIdPtr);
        if(rc) {
            perror("Error :");
            exit(BAD_EXIT);
        }
    }
    return 0;
}
cse
  • 4,066
  • 2
  • 20
  • 37
Yos
  • 301
  • 4
  • 15
  • 4
    You should wait for the threads created using `pthread_join` – Karthick May 02 '18 at 11:33
  • 2
    Your program exits as soon as the **main thread** exits. Well, in your code, that happens right after you created all the other threads, while they are still blocked at `sleep(1)`. –  May 02 '18 at 11:35
  • 2
    side notes: there are simpler ways to initialize an array of strings, the `malloc()` can be avoided (e.g. using an array of `int`, or maybe with `intptr_t` cast to `void *`), and there's a standard define for an error exit: `EXIT_FAILURE` -- so don't define your own. –  May 02 '18 at 11:37
  • @FelixPalmen thank you for the side notes, and by the way, isn't the only reason we use `malloc` is that if `PrintHello` somehow modified `taskIdPtr` then other iterations of the loop wouldn't be affected? But if `PrintHello` doesn't modify `taskIdPtr` coulnd't I pass `t` as the last argument to `pthread_create`? – Yos May 02 '18 at 11:48
  • 1
    The reason to pass a pointer is that `pthreads` define the interface that way. `void *` has the advantage that you can actually pass (a pointer to) anything. If you'd just pass a pointer to your `t`, however, by the time your thread function accesses it, it will be changed by the `for` loop. So when passing a pointer to an `int`, you have to make sure each thread gets its own instance. But `malloc()` isn't the only way to do that, you could just prepare an `int` array (like you do with your strings) and pass each thread a pointer to one of its elements. –  May 02 '18 at 11:54
  • 2
    @Yos just passing `t` (**not** a pointer) wouldn't be correct, there's no guarantee any value of an `int` "survives" a roundtrip when converted into a pointer and back. But there's the type `intptr_t` that gives that guarantee, so you could simply make your `t` of that type and pass it directly, cast to `void *`. –  May 02 '18 at 11:55
  • @FelixPalmen I tried passing `&t` to `pthread_create` and using join and I get an output of 8 strings which **all** say "Task id = 0, message = English: Hello World!". According to your explanation I'd think that for example currently `t=0` then I pass it to `pthread_create` and by that time `t=1` already. Then `PrintHello` would print out the 2nd string and id=1. But this is not what's happening. Did I understand your explanation correctly? – Yos May 02 '18 at 12:03
  • 1
    @Yos I just told you passing `&t` is the **one** thing you **can't** do. What I explained was an example of what could happen -- it's not possible to predict as you can't know how your OS will schedule the threads. The thing is, your `main()` continues to modify `t`, so passing a pointer to it to a thread will of course not work correctly. –  May 02 '18 at 12:12
  • 1
    @FelixPalmen `intptr_t` is able to hold any pointer value. Nowhere it is said that a pointer is able to hold any `intptr_t` value. Round-trip of pointer to intptr_t to pointer is guaranteed to work. Round trip of intptr_t to pointer to intptr_t is not. In practice it will work for small enough integers of any type, but no guarantee is given, – n. m. could be an AI May 02 '18 at 12:17
  • @n.m. yes, I thought it was guaranteed in POSIX though .... but can't find a reference right now, maybe I'm remembering wrong here –  May 02 '18 at 12:21
  • By the way, another possible solution of the problem is to call `pthread_exit(NULL)` as the last thing from `main` without calling `pthread_join`. Then `main` will block and wait for other threads to terminate. Found this here: https://computing.llnl.gov/tutorials/pthreads/ – Yos May 02 '18 at 12:53

2 Answers2

4

The problem is that your main thread completes its execution and exits before any other child thread even prints a single message(Since in your child thread you are waiting for 1 second before doing anything).

As soon as the main thread exits, all other child threads also exits and you don't see any message on your screen.

Following is an extract of linux manual page of pthread_create():

The new thread terminates in one of the following ways:

  • It calls pthread_exit(3), specifying an exit status value that is available to another thread in the same process that calls pthread_join(3).
  • It returns from start_routine(). This is equivalent to calling pthread_exit(3) with the value supplied in the return statement.
  • It is canceled (see pthread_cancel(3)).
  • Any of the threads in the process calls exit(3), or the main thread performs a return from main(). This causes the termination of all threads in the process.

To solve this problem you should use int pthread_join(pthread_t thread, void **retval).

Following is corrected code. See it working here:

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

#define BAD_EXIT 1
#define NUM_TASKS 8

char * messages[NUM_TASKS];

void * PrintHello(void * taskIdPtr) {
    int taskId;
    sleep(1);
    taskId = *((int *) taskIdPtr);
    printf("Task id = %d, message = %s\n", taskId, messages[taskId]);
    free(taskIdPtr);
    pthread_exit(NULL);
}
int main(int argc, char ** argv) {
    pthread_t threads[NUM_TASKS];
    int * taskIdPtr;
    int rc, t;
    messages[0] = "English: Hello World!";
    messages[1] = "French: Bonjour, le monde!";
    messages[2] = "Spanish: Hola al mundo";
    messages[3] = "Klingon: Nuq neH!";
    messages[4] = "German: Guten Tag, Welt!";
    messages[5] = "Russian: Zdravstvytye, mir!";
    messages[6] = "Japan: Sekai e konnichiwa!";
    messages[7] = "Latin: Orbis, te saluto!";

    for(t = 0; t < NUM_TASKS; t++) {
        taskIdPtr = (int *) malloc(sizeof(int));
        *taskIdPtr = t;
        printf("Creating thread %d\n", t);
        rc = pthread_create(&threads[t], NULL, PrintHello, (void *) taskIdPtr);
        if(rc) {
            perror("Error :");
            exit(BAD_EXIT);
        }
    }

    for(t = 0; t < NUM_TASKS; t++)
    {
        pthread_join(threads[t], NULL);
    }
    return 0;
}
cse
  • 4,066
  • 2
  • 20
  • 37
2

The main process is exiting before the newly created threads gets executed. So you should wait for the threads created using pthread_join.

As per the man page of pthread_join

int pthread_join(pthread_t thread, void **retval);

The pthread_join() function waits for the thread specified by thread to terminate. If that thread has already terminated, then pthread_join() returns immediately. The thread specified by thread must be joinable.

Karthick
  • 1,010
  • 1
  • 8
  • 24