2

I am trying to create a multi-thread program that creates a thread for every file that is taken it (file1.txt to file20.txt) and I need to pass in a variable into the function using pthread_create. I am having difficulties passing my int value into the function because pthread_create only takes in void* so my function has to take in a void*.

I have some experience with pointers but I don't understand the concept of a void pointer very well. As far as I have been able to figure out is that my pthread_create is suppose to look something simular to this pthread_create(&tids[i], NULL, getSales, (void*)i );

in main():

using namespace std;

int main(int argc, char **argv){
    int numfiles = 20;
    // Thread_ID's
    pthread_t tids[numfiles];

    // create threads
    for(int i = 1; i <= numfiles; i++){
        pthread_create(&tids[i], NULL, getSales, (void*)i );
    }

    // wait until threads are done
    //join threads together so they run simultaneously
    for(int i = 1; i <= numfiles; i++){
        pthread_join(tids[i], NULL);
    }
}

currently in getSales:

void* getSales(void* fileNum){
    int* num = (int*)fileNum;
    cout << &num <<endl;
    pthread_exit(0);
}

currently when I compile this code it crashes and giving me multiple lines of back trace but it seems that the error is just the getSales function currently the output should print the numbers 1 to 20 in random order because the function is multi-threaded.

gcc staplemax.cpp -lpthread -o staplemax
staplemax.cpp: In function ‘int main(int, char**)’:
staplemax.cpp:114:57: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
         pthread_create(&tids[i], NULL, getSales, (void*)i );
                                                         ^
/tmp/ccf6zgpk.o: In function `getSales(void*)':
staplemax.cpp:(.text+0x10e): undefined reference to `std::cout'
staplemax.cpp:(.text+0x113): undefined reference to `std::ostream::operator<<(void const*)'
staplemax.cpp:(.text+0x118): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)'

edit:

I'm now compiling with g++ staplemax.cpp -lpthread -o staplemax instead of gcc staplemax.cpp -lpthread -o staplemax as recommended.

I also changed main due to it being pointed out that &i was likely changed with the call of each loop of the for loop. my code currently looks like this

void* getSales(void* fileNum){
    int num = *(int*)fileNum;
    cout << num <<endl;
    pthread_exit(0);
}

int main(int argc, char **argv){
    int numfiles = 20;

    // Thread_ID
    pthread_t tids[numfiles];
    int args[numfiles];

    // create thread
    for(int i = 0; i < numfiles; i++){
        args[i] = i+1;
        pthread_create(&tids[i], NULL, getSales, &args[i] );
    }

   // wait until thread is done  //join
    for(int i = 0; i < numfiles; i++){
        pthread_join(tids[i], NULL);
    }
}

This compiles (with g++) but not all numbers are 1-20

3
2
9
1
10
6
87

5
4
11
12
13
14
15
16
17
18
19
20
CSstudent
  • 669
  • 2
  • 7
  • 27

2 Answers2

4

The classical solution to this is to have an array of objects that represent the argument to each pthread. That is, each thread will have an associated unique location that will hold its argument. You just need to pass a pointer to that unique location.

Here's an example:

void *pthread_func(void *_arg) {
    int arg = *(int*)_arg; // we're receiving an int
    // ...
}

int main() {
    pthread_t threads[20];
    int       args[20]; // unique arg location for each thread

    for (int i = 0; i < 20; ++i) {
        args[i] = (whatever you want to pass)

        // create the thread, give it its unique arg location
        pthread_create(&threads[i], NULL, pthread_func, &args[i]);
    }

    // ... join the threads, etc. ...
}

The problem with passing the loop variable by address (i.e. &i) is that it might (most definitely will) be changed before each thread completes.

For instance, in iteration i=0 you pass &i. Unless you immediately join the newly-created thread, the next loop iteration changes i to 1, which will most certainly mess up the thread you just created. Even worse, it's possible the i=0 thread never even sees 0, as the thread creation could be delayed depending on what the system is doing. Since they all point to the same location, they all observe the same value (aside from pedantic memory ordering considerations).

Edit:

Because the number of threads you use will (should) always be small (since you should never make more running threads than you have processors to run them), the memory overhead for the above method is small. However, if you're passing an integer type and you want to want to save as much space as possible you can cast it into a void* (because pointers are also an integer type).

The only caveat is that casting between integer types and pointers is only allowed if they have the same number of bits. To facilitate this, use intptr_t or uintptr_t as an intermediary.

Here's an example of that:

void *pthread_func(void *_arg) {
    int arg = (int)(intptr_t)_arg; // we're receiving an int encoded as void*
    // ...
}

int main() {
    pthread_t threads[20];

    for (int i = 0; i < 20; ++i) {
        void *arg = (void*)(intptr_t)(whatever you want to pass);

        // create the thread, give it its packed argument
        pthread_create(&threads[i], NULL, pthread_func, arg);
    }

    // ... join the threads, etc. ...
}
Cruz Jean
  • 2,761
  • 12
  • 16
  • This is what you would need for passing arguments larger than a `void*`, but as the original argument fit, it's not necessary (note the original code passed `(void*)i` not `&i`) – Ben Voigt May 13 '19 at 04:36
  • Pedantic note: an `intptr_t` is capable of holding any pointer. Hence the roundtrip `void*` to `std::intptr_t` to `void*` is guaranteed to return the original pointer value. However, there is no formal guarantee that `std::intptr_t` to `void*` to `std::intptr_t` will always yield the initial value. There may be different values of `std::intptr_t` that represent the same pointer value. In practice, that might not be an issue, though. – Julius May 13 '19 at 14:30
  • @Julius In that case wouldn't using `std::uintptr_t` solve that problem? It wouldn't have the issue of potentially having different encodings for the same value. – Cruz Jean May 13 '19 at 18:17
  • @CruzJean: No, the unsigned version has the same problem. Let me quote 7.20.1.4 of [N1570](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf): "The following type designates an unsigned integer type with the property that any valid pointer to `void` can be converted to this type, then converted back to pointer to `void`, and the result will compare equal to the original pointer: `uintptr_t`". Note that there is only this roundtrip described (and not the other one). – Julius May 13 '19 at 19:08
  • @Julius Ah, I see. Well then, what about punning hacks via `std::memcpy`? One could `memcpy` the value on top of a `void*` and then `memcpy` it back out. That doesn't violate strict aliasing and a smart compiler will compile it into a `mov` instruction and thus still be efficient. – Cruz Jean May 13 '19 at 19:33
  • @CruzJean: I am not absolutely sure whether the `memcpy` approach is valid from a language-lawyer point of view. It may help that `void*` is [trivially copyable](https://en.cppreference.com/w/cpp/types/is_trivially_copyable), but I am not an expert in that. The clean and obvious thing to do is passing a pointer to some **good** chunk of memory. – Julius May 13 '19 at 20:55
3

You cast an int to void*. To undo it, cast a void* back to an int. So:

int* num = (int*)fileNum;

Should be:

int num = (int)fileNum;
David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • 1
    if I make that change I still get and error `cast from ‘void*’ to ‘int’ loses precision` on that line and a warning `cast to pointer from integer of different size` on the line that `pthread_create` is called on. – CSstudent May 13 '19 at 02:47
  • if I change `pthread_create(&tids[i], NULL, getSales, (void*)i );` to `pthread_create(&tids[i], NULL, getSales, (void*)&i );` it removes the warning, but I'm still getting on the `int num = (int)fileNum;` – CSstudent May 13 '19 at 02:55
  • 1
    @CSstudent Now you're creating a dangling pointer; you can't program by guessing ;) Really you _should_ be dynamically allocating some structure that contains the data you need. The compiler is correct here in both cases; this is not a good way to use pointers, even for this. (A roundtrip through `intptr_t` _may_ solve your problem in this case) – Lightness Races in Orbit May 13 '19 at 03:06
  • @LightnessRacesinOrbit: Your comment refers to `intptr_t`, but I believe that `intptr_t` is not guaranteed to work for the roundtrip which is required here. My comments to the other answer contain an explanation. I can imagine that you know all the subtle details and hide them behind the word "may". However, in the current form your reference to `intptr_t` may motivate people to build their hacky solution based on that. Are you willing to enlighten us? – Julius May 13 '19 at 21:07
  • @Julius You can definitely take a `intptr_t` and [send it safely on a roundtrip through `void*`](https://stackoverflow.com/a/9492910/560648). Granted, it is not guaranted to hold any `int` you throw at it during an earlier stage of your algorithm, but I have assumed that that is taken care of aforehand ;) – Lightness Races in Orbit May 13 '19 at 23:19
  • @LightnessRacesinOrbit: Thanks for the link to that related answer. I have read the question, all the answers, and all the comments. I could not find a convincing statement on the roundtrip intptr_t -> void* -> intptr_t. I know that it works in practice, but I am interested in the guarantees from the C and C++ standards. My own research led me to this [interesting ticket from the Tor project](https://trac.torproject.org/projects/tor/ticket/29537) (C99). Maybe I should stop bothering others with comments and ask a real question instead. – Julius May 14 '19 at 05:50
  • @Julius Actually you may be right; I can't find evidence for what I was saying. Perhaps I imagined it! That would indeed be a good question; I'm surprised how hard it is to find one right now. – Lightness Races in Orbit May 14 '19 at 09:51