1
#include <stdio.h>
#include <pthread.h>

typedef struct {
    int threadNum;
}thread_args;

void thread_func(void*vargp){
    thread_args*id=(thread_args*)vargp;
    printf("%i\n",id->threadNum);
}

int main() {
    for(int i=0;i<20;i++) {
        pthread_t id;
        thread_args args;
        args.threadNum=i;
        pthread_create(&id,NULL,thread_func,(void*)&args);
    }
    pthread_exit(NULL);
    return 0;
}

Adapted from https://www.geeksforgeeks.org/multithreading-c-2/.

So this is expected to output:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

But shuffled in a random order to account for the concurrency of the threads.

The issue here is that it actually prints out this:

4
9
10
5
11
12
13
8
4
4
17
6
18
7
15
19
6
14
19
16

As you can see, there are duplicate numbers and 0-3 are just plain skipped.

I have done concurrency before in other frameworks before, and I have seen similar issues: what is happening here is that the i is being passed as a reference (I think!) and so when the for loop increments i, it is incremented in all thread argument variables.

How can I avoid this?

NOTE: Everything is linking 100% properly and I'm on macOS.

PS: Sorry if this is a duplicate, I'm not very experienced with this.

mackycheese21
  • 884
  • 1
  • 9
  • 24
  • Have you ever heard of race conditions? Google it. – S.S. Anne Mar 26 '19 at 23:52
  • I don't care about order of thread execution, and there are no side-effects other than printing to the console. – mackycheese21 Mar 26 '19 at 23:54
  • Yes, but `pthread.threadNum` is being changed every time the program executes. Try commenting out `pthread_exit` and try again. Sadly, I'm not very experienced in threads either. – S.S. Anne Mar 27 '19 at 00:00

2 Answers2

4

You are having an UB at your for loop. You are creating an variable called args, where you assign a value to it, pass as reference to your thread, for later execution, and destroy it at the end of your for loop. Then you do it again, possibling overwritting this region.

To solve that problem, I suggest this modification:

int main() {
    thread_args args[20] = {0};
    pthread_t id[20] = {0};

    for(int i=0;i<20;i++) {
        args[i].threadNum=i;
        pthread_create(&id[i],NULL,thread_func,(void*)&args[i]);
    }

    for(int i = 0; i < 20; i++)
        pthread_join(id[i], NULL);
    return 0;
}
Amadeus
  • 10,199
  • 3
  • 25
  • 31
  • 1
    `malloc` + `free` avoids storing all the args all the time. – NoOneIsHere Mar 27 '19 at 00:14
  • @NoOneIsHere — using `malloc()` + `free()` gives problems unless you keep a track of all the allocated pointers and only free them after you know that the thread using it has ended — which is worse than using an array of `thread_args`. Using `malloc()` without `free()` leaks, but that's moderately safe in the context of a tiny program like this, but hardly for long-running production threaded code. – Jonathan Leffler Mar 27 '19 at 05:20
  • What's a UB? I haven't seen that acronym before. – mackycheese21 Mar 27 '19 at 15:19
  • @mackycheese21 sorry, I guess this a terminlogy for C++ (https://stackoverflow.com/questions/2766731/what-exactly-do-ib-and-ub-mean), it means undefined behaviour, I guess the correct name here would be data race – Amadeus Mar 27 '19 at 16:20
  • This answer is still incorrect: as "main" thread exit, one should assume its stack disappear with it, hence `args[]` became undefined. To address this issue, other threads must be joined from "main" thread with `pthread_join()` before leaving. – Yann Droneaud Mar 27 '19 at 17:32
  • 1
    @YannDroneaud I had put the original answer to solve the immediate problem that was the one of deallocating a block of memory that was in use, without looking for the rest of the program, but you are right, to be correct it is necessary to wait for the other threads to finish before finishing the main thread. I've changed it for take account of this. Thanks for the comment – Amadeus Mar 27 '19 at 17:55
1

This is, in fact, a race condition. You pass a void pointer to the argument struct, but (likely) the same memory address is reused for each argument struct. Therefore, when you later access it, you are likely to read modified memory. Try this:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
typedef struct {
    int threadNum;
}thread_args;

void thread_func(void* vargp){
    thread_args* id = (thread_args*)vargp;
    printf("%i\n", id->threadNum);

    free(vargp);
}

int main() {
    for(int i=0;i<20;i++) {
        pthread_t id;
        thread_args* args = malloc(sizeof(thread_args));
        args->threadNum = i;
        pthread_create(&id, NULL, thread_func, (void*)args);
    }
    pthread_exit(NULL);
    return 0;
}

Thanks to Kamil Cuk for pointing out another race condition.

Note that this snippet might still leak because the code never joins the threads, so the free() might never be called.

NoOneIsHere
  • 1,054
  • 16
  • 28
  • Your implementation is still a race condition, now between `free` and `thread_func`. Move `free` to `thread_func`. The usage of `volatile` is unjustified here. Why would you choose to not optimize the accesses to `thread_args`? – KamilCuk Mar 27 '19 at 00:13
  • Now you're leaking like a sieve. Create an array `thread_args args[20];` (outside the loop) and initialize each thread argument and pass it. You also don't keep track of the threads and join with them — though that's also a problem with the code in the question. – Jonathan Leffler Mar 27 '19 at 05:16
  • @JonathanLeffler true. I just thought that freeing the data when it wasn't needed would work, but it seems it still leaks. – NoOneIsHere Mar 27 '19 at 16:58
  • I admit I missed the `free()` in the thread function; I normally expect the code that allocates to manage the freeing too (so I wasn't looking for it). You comment about "I though it would work, but it seems it still leaks". That's a little surprising. However, this might be because you use `pthread_exit()` in the `main()` function instead of `pthread_join()` — I'd need to study what's supposed to happen. The `(void *)` cast in the call to `pthread_create()` isn't needed — the compiler will convert any pointer to a void pointer to match a function prototype without further prompting. – Jonathan Leffler Mar 27 '19 at 17:09
  • @JonathanLeffler I tried to change the original as little as possible, so the leak can't really be managed. I'll add more details. – NoOneIsHere Mar 27 '19 at 17:17
  • threads must be joined for resources such as stack to be released. – Yann Droneaud Mar 27 '19 at 17:34
  • @YannDroneaud I'm aware, but the original code doesn't join the threads either. – NoOneIsHere Mar 27 '19 at 17:44
  • But it's needed to remove the leak you're complaining about: "but it seems it still leaks." – Yann Droneaud Mar 27 '19 at 17:48
  • @YannDroneaud I know that. I just don't want to change things unnecessary to the original problem. – NoOneIsHere Mar 27 '19 at 19:00