3

I am for my own amusement tinkering with this piece of code:

#include <windows.h>
#include <process.h>
#include <stdlib.h>
#include <stdio.h>

void print_message(void *param)
{
    int thread_counter = *((int*)param);
    printf("Thread #%i\r\n", thread_counter);
    _endthreadex(0);
}

int main()
{
    unsigned thread_ID;
    HANDLE thread_handle;

    int thread_count = 10;

    HANDLE thread_handles[thread_count];

    for(int i = 0; i < thread_count; i++) 
    {
        thread_handles[i] = (HANDLE)_beginthreadex(NULL, 0, &print_message, &i, 0, &thread_ID);
        thread_handle = thread_handles[i];
    }

    WaitForMultipleObjects(thread_count, thread_handles, TRUE, INFINITE);
    return EXIT_SUCCESS;
}

a

Output
Thread #8
Thread #10
Thread #10
Thread #10
Thread #10
Thread #10
Thread #10
Thread #10
Thread #10
Thread #10

I think the problem is that I am passing a reference to variable i, and the variable i is being incremented in the for loop while the threads are using/manipulating this reference.

How do i avoid the race condition, and pass the value of variable i to the thread function?

I would also like to pass strings/char arrays to the thread function, but I do not seem to understand how to do this.

I am using the Tiny C Compiler (TCC) on Windows 10

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
user1359448
  • 1,539
  • 3
  • 14
  • 23

2 Answers2

3

Don't pass a pointer to i, pass the value stored in i itself:

void print_message(void *param)
{
    int thread_counter = (intptr_t)param;
    printf("Thread #%i\r\n", thread_counter);
    _endthreadex(0);
}

// ... rest of code ...
    // We cast the value in i to a pointer width integer value, intptr_t
    // to explicitly match the size of the void* we're smuggling it in
    thread_handles[i] = (HANDLE)_beginthreadex(NULL, 0, &print_message, (void*)(intptr_t)i, 0, &thread_ID);
// ... rest of code ...

If all you're passing is a single, smaller than pointer width value, you can smuggle its value in as the "pointer" value itself. In this case, you just upcast it to intptr_t (an integer type matching the width of a pointer), and downcast it on extraction.

Because the actual value is passed, not a pointer to it, the copy is made before _beginthreadex is called, and modifying i thereafter doesn't change anything.

If the data to be passed is larger, then you're stuck either imposing a barrier of some sort (to ensure the value is read before the main thread touches i again) or with dynamic memory usage (allocate space, copy in values, pass pointer to allocated memory to thread, thread extracts values before freeing the space). A third option is an array of the values, one for each thread (so the first thread receives &arr[i] where i is 0, the next &arr[i] for i == 1, etc.).

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • nice I learned about `intptr_t`now. I will steal it for my answer. :P But should thread_counter not also be of ype `intptr_t`? – Kami Kaze Jun 28 '19 at 14:00
  • Re, "pass `i` itself." IMO, when somebody is struggling to understand pointers and references, we need to be very careful about what we say. Any experienced programmer will immediately see that you meant _the value_ of `i` when you said, "`i` itself", but for newbies, it's not always that obvious. – Solomon Slow Jun 28 '19 at 14:45
  • @KamiKaze: Given the original value is `int`, we already know it's safe to store `thread_counter` as `int`. Yes, it's safer (given that `int` can theoretically be larger than `intptr_t`) to use `intptr_t` consistently (have both `i` and `thread_counter` be `intptr_t` from the start), but if `i` is `int`, I consider it better for symmetry to have `thread_counter` be `int` as well. The fact we use `intptr_t` is kind of an implementation detail of how you have to smuggle integers in pointer variables, not a useful type for general storage in and of itself. – ShadowRanger Jun 28 '19 at 16:09
  • @SolomonSlow: True. I'll tweak the wording for clarity. – ShadowRanger Jun 28 '19 at 16:09
  • Its the other way around actually. `intptr_t` has 64bits on x64 systems while `int` often has just 32bits. So this way you can produce an integer overflow which is UB for signed intergers. Also what should the double cast accomplish? `(void*)(intptr_t)i` – Kami Kaze Jul 01 '19 at 07:14
2

You could pass the value instead of the pointer similiar to this.

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

void print(void *foo){
    intptr_t bar = (intptr_t)foo; 
    printf("%" PRIdPTR "\n",bar); 
}

int main(void)
{
    intptr_t i=1;
    print((void*)i);
    return 0;
}

You just have to be careful that the function really takes this as a value. In the function itself you have to make sure that the variable you cast to can take the size of a pointer. (this is done by using intptr_t)

To avoid raceconditions in general you can use mutexes, but this is not really useful here.

You could signal somehow that the value was read in the thread and let the loop wait until then but this would make the threading quite useless.

Kami Kaze
  • 2,069
  • 15
  • 27