1

I am stuck on this and cannot seem to figure out what is going on. For background, I am trying to pass a struct into 3 pthreads, then modify that struct in the threads, then store back each struct into the main thread, and add up the partial results gotten by each one in order to speed up this part of my code that seems to be a main bottleneck in performance. I have used code very similar to this in another project written in C, but when I copied it into this C++ program and modified it to fit this problem, I have run into some weird errors.

First of all, I had to make some casts that I didn't have to make in C in order to get it to compile. Since I included the thread_work function in the class as a method, it saw the type as (void * (AIShell::*)(void *), so I needed to cast it as (void * (*)(void *), which allows it to compile and run, but it gives me a compiler warning. The reason I included it as a method rather than just a function is so that I can call another class method from within this one, so it is required. Is there any way I should revise my code to make this go away, or would this just be an unfortunate side effect of using my thread function in a class?

AIShell.cpp: In member function ‘double AIShell::heuristic(int**)’:
AIShell.cpp:173:78: warning: converting from ‘void* (AIShell::*)(void*)’ to ‘void* (*)(void*)’ [-Wpmf-conversions]
   th = pthread_create(&new_threads[i], &attr, (void * (*)(void *)) &AIShell::thread_work, (void *) (se+i));

Secondly, and most importantly I get a segfault when running my code. I have narrowed down where the problem happens, and it happens in my thread_work function whenever I try to access a member of the struct that I passed into the thread (ie doing se->start_count):

125 struct start_end
126 {
127     int ** board;
128     int start_col;
129     int end_col;
130     int start_row;
131     int end_row;
132     double total;
133 };
134
135 void * AIShell::thread_work(void * arg)
136 {
137     struct start_end * se = (struct start_end *) malloc(sizeof(struct start_end));
138     se = (struct start_end *) arg;
139     printf("\nse->total = %lg; se->start_row = %d; se->start_col = %d\n\n", se->total, se->start_row, se->start_c    ol);
140     fflush(stdout);
141     for (int row = se->start_row; row != se->end_row; row++)
142     {
143         for (int col = se->start_col; col != se->end_col; col++)
144         {
145             se->total += check8(se->board, col, row);
146         }
147     }
148     printf("asdfasdfasfdasdfasdfasdfasdfasdfasdfasdf\n");
149     fflush(stdout);
150     pthread_exit((void *)se);
151 }

When it gets to this part of the code, which is what is run by each thread, it initially failed on line 141 where the for loop started, but after adding the print statement on line 139, it fails there, which confirms that the problem is with referencing the struct members.

However, if I add an "&" before "arg" in line 138 so that it looks like this:

138     se = (struct start_end *) &arg;

it doesn't segfault anymore, but it gets garbage values which breaks some of my code later on. To my knowledge I am properly assigning values to the structs before they are passed in to the thread, so I am not really sure what is going on here.

This code is the method that acts as the "main thread" which initializes the structs, spawns the other threads, and is supposed to get back the results from each one and add them up to get the final result. It also acts as a thread for this itself so I used a similar code segment here that I did in the above function so I would be using all of my resources ;)

153 double AIShell::heuristic(int ** board)
154 {
155     pthread_t new_threads[3] = {0};
156     pthread_attr_t attr;
157     int th;
158     long t;
159     struct start_end * se;
160     se = (struct start_end *) malloc(3*sizeof(struct start_end));
161
162     pthread_attr_init(&attr);
163     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
164
165     for (int i = 0; i <3; i ++)
166     {
167         (se+i)->board = copy(board);
168         (se+i)->start_row = numRows/4*i;
169         (se+i)->end_row = numRows/4*(i+1);
170         (se+i)->start_col = numCols/4*i;
171         (se+i)->end_col = numCols/4*(i+1);
172         (se+i)->total = 0;
173         th = pthread_create(&new_threads[i], &attr, (void * (*)(void *)) &AIShell::thread_work, (void *) (se+i));
174         if (th)
175         {
176             perror("pthread_create");
177             exit(1);
178         }
179     }
180
181
182     int start_row = numRows/4*3;
183     int end_row = numRows;
184     int start_col = numCols/4*3;
185     int end_col = numCols;
186     int total = 0;
187
188     for (int row = start_row; row != end_row; row++)
189     {
190         for (int col = start_col; col != end_col; col++)
191         {
192             total += check8(board, col, row);
193         }
194     }
195
196     for (int i = 0; i != 3; i++)
197     {
198         th = pthread_join(new_threads[i], (void **) (se+i));
199         if (th)
200         {
201             perror("pthread_join");
202             exit(1);
203         }
204     }
205
206     for (int i = 0; i != 3; i++)
207     {
208         total += (se+i)->total;
209         free((se+i));
210     }
211
212     pthread_attr_destroy(&attr);
213
214     free(se);
215
216     return total;
217 }

I realize that doing it this way is very "C" of me and I could be doing it with "new" or whatever, but as I said, I adapted the code from a "C" project to use in this "C++" project, and would like to do it this way without having to re-write everything. I also am not positive I am "freeing" correctly for this, but it doesn't even get there in my code yet, so that is more of a secondary issue here. So with all that out of the way, does anyone see what it is that I am doing wrong here? I have tried so many things, and just can't seem to get it to work :/

Sean
  • 13
  • 3
  • Does this work if you only create a single pthread? Just change the for loop to i < 1 instead of i < 3. Curious if thread_work still gets garbage in that case. – Erix Dec 05 '16 at 02:13
  • No, unfortunately that doesn't make a difference. I get this for the thread: se->total = 6.92887e-310; se->start_row = -1596268816; se->start_col = 16921216 – Sean Dec 05 '16 at 02:17
  • Ok, can you do this before the for loop, do fprintf(stderr, "before %p\n", se); and then inside thread_work, right at the beginning, do fprintf(stderr, "after %p\n", arg); I just want to see what the address of se is and if the thread_work is getting the same address – Erix Dec 05 '16 at 02:20
  • Okay, so this is interesting, there is definitely a problem here because it gets "before 0x116b280" and then "after (nil)" twice for the two threads it is able to spawn before it dies – Sean Dec 05 '16 at 02:28
  • @Sean the next thing I'd do is fix that warning – Erix Dec 05 '16 at 02:48
  • @Erix Okay, I will work on that. I will look into "bind" and see how I can fix that part so that the warning goes away, and then we can work on that next part. – Sean Dec 05 '16 at 02:52
  • You're not going to be able to use pthreads with a non-static member function. Either use a static wrapper that takes a pointer to an object or use `std::thread` instead of using pthreads directly. – Miles Budnek Dec 05 '16 at 02:56
  • @MilesBudnek Okay, I will see if I can make it static then, and see if that helps. – Sean Dec 05 '16 at 03:00
  • @Sean don't even bother with that. Just make your array of structs a member of the class itself, and don't pass the last arg into pthread_create. Your threads will then have access to the array since they're executing on the same object. This is how I usually do it in the real world - I never use the create to pass args – Erix Dec 05 '16 at 03:00
  • @Erix Okay, that sounds pretty manageable, I will try that. If I do it like that, however, would I still be able to pass the thread_work function to the thread as it stands? – Sean Dec 05 '16 at 03:05
  • @Erix I just realized that that presents a problem... I need to tell each thread which part of the 2d array to look at, and in order to do so, I need to pass it a struct... Sigh, tough problem lol EDIT: nevermind, I can pass it an int that tells it how it knows which one to check. Doing it this way though can result in race conditions, and other editing problems, I will need to add locks now lol. This might take a while to revise – Sean Dec 05 '16 at 03:19
  • @Sean I was just referring to the bind stuff. You still need to fix what the warning is referring to. – Erix Dec 05 '16 at 03:23
  • @Erix Okay, and how would I do that? Isn't that with the bind? Or with the static part? Also, I realized that I still need to pass in at the very least an integer to the thread, and I found another solution that was on SO for that, and I did it exactly how they said, and it doesn't even get anything when I pass in an int! Is that possibly because of the compiler warning I am getting? – Sean Dec 05 '16 at 04:07
  • @Erix EDIT: Okay nevermind, I got the static wrapper to work so that the compiler error goes away, however, I am still not able to access the int that I pass to the threads... Any ideas on this? This is how my pthread_create looks now: `th = pthread_create(&new_threads[i], &attr, &AIShell::thread_work_helper, new int(i));` It gives the same problem as before, where any time I try to access "i" it segfaults, so I can't even try and print it – Sean Dec 05 '16 at 04:21
  • @Sean Yes it's the warning. Your case is a little different but there's an easy way. Create a little container object that looks like this class Context { AIShell* theObject; int number; } Create a static function that looks like this static void* ThreadFunction(void* context) Then, where you are currently calling pthread_create, instantiate a Context object with "this" and the number (1 through 3). Pass ThreadFunction as the start_routine, and the Context object as the last arg. Then, within ThreadFunction, you can call context->theObject->thread_work(context->num) – Erix Dec 05 '16 at 04:23
  • @Erix Okay, I have done all that you said except for the part where you call "context->theObject->thread_work(context->num)". I tried putting that but it didn't work because it is still void * type, and even casting it to (Context) before that line didn't work either. Is there something else I need to do to make that part work? – Sean Dec 05 '16 at 05:00
  • @Sean yes you need to cast it to a Context. What exactly did you try and how didn't it work? – Erix Dec 05 '16 at 05:02
  • @Erix Well, I originally tried add just (Context) before what you put so it would look like `(Context) context->theObject->thread_work(context->num)` but that didn't work, so I figured I needed to copy it into an object of type Context, so I did `Context c; c = (Context) arg; c.theObject->thread_work(arg.num);` but then I realized I need a constructor for that in the class. Is that the direction you inteded for me to go, or is there a simpler way to do it? – Sean Dec 05 '16 at 05:07
  • @Sean it should look like this Context* ctxt = (Context*) arg; ctxt->theObject->thread_work(ctxt->num) – Erix Dec 05 '16 at 05:10
  • @Erix Wow, that worked! It is running till completion now!! Thanks you so much for your help! However, for some reason it is slower than when I was running it without threads, which makes no sense... – Sean Dec 05 '16 at 05:23
  • @Sean there's a lot of overhead in running threads. If it was already fast, threading it won't necessarily make it faster. Especially if you're running on something that doesn't have available CPUs. glad it worked for you. – Erix Dec 05 '16 at 05:27
  • @Erix Ah I see... Hmm, well, previously it ran in approximately 7-10 seconds for the whole bit of code, and when I commented out the parts that I threaded, it got responses almost instantaneously, so I figured if I multithreaded the section I knew was taking a while, it would speed things up, but maybe I just need to multithread the whole program haha. Either way I really appreciate all of your help! I wanna make sure you get credit for all the help you've been, so if you submit an answer to my question on this page rather than a comment here I will mark it as a solution! – Sean Dec 05 '16 at 05:34
  • @Sean cool I added an answer. Thanks – Erix Dec 05 '16 at 05:38

2 Answers2

1

You're leaking memory there, for one. you malloc memory and assign pointer to se in thread function, then assign value of arg to the same pointer. Memory , allocated by first line of thread_work will be lost.

Second, why you cast so (void * (*)(void *)) &AIShell::thread_work?

Function declared as

void * AIShell::thread_work(void * arg)

doesn't require such cast unless.. it's a method of class and AIShell is class, not namespace. Is AIShell a class then? In that case you either need a static function or use std::bind to generate a wrapper for it, non-static methods actually have hidden argument of "this".

Line

se = (struct start_end *) &arg;

is wrong because you get address of argument arg(which is in stack supposedly?) and convert it to pointer to struct, while actually that memory contains pointer. It doesn't produce segfault because most likely you appear to access stack or data segment, no violation there. Debug program and check value of arg in worker and compare it to value of se+i given to pthread_create. That is value of pointer in first place.

Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42
  • Hmm, okay how should I revise that first part you are referring to then so that I don't leak memory? And yes I cast it as that because it is in a class called AIShell. And okay, now if I do that, would I still be able to reference a method from the class? I still need to use a different method from within the thread_work method so that would be necessary. And yeah I just tested the value of arg at the beginning of thread_work and it came up as (nil). Why would it be doing that? – Sean Dec 05 '16 at 02:39
  • 1
    @Sean Well, if pthread library "thought" it is a single argument function, it gave it a single argument.. pointer to `se+i` went into `this` pointer instead. if you will use the bind wrapper, you should be able do this .. http://stackoverflow.com/questions/15264003/using-stdbind-with-member-function-use-object-pointer-or-not-for-this-argumen – Swift - Friday Pie Dec 05 '16 at 02:47
  • I will try and bind the function, and see if that helps with this problem. I'm not really sure how to use it, but I will try and figure it out. – Sean Dec 05 '16 at 02:54
  • A bind result isn't going to be able to decay into a `void(*)(void*)` because it contains state. C functions and C++ objects simply don't mix well. – Miles Budnek Dec 05 '16 at 02:58
  • @Miles Budnek Why static worker that calls bound function? if one can't avoid using object instance, using some wrapper is the only way – Swift - Friday Pie Dec 05 '16 at 06:35
  • A static wrapper that takes a bind object as a parameter will work. My comment was just that you can't do something like `pthread_create(thread, attr, std::bind(&AIShell::thread_work, this, se + i), nullptr);` – Miles Budnek Dec 05 '16 at 06:39
0

You need to fix that warning.

Your case is a little different but there's an easy way. Create a little container object that looks like this

class Context { AIShell* theObject; int number; }

Create a static function that looks like this

static void* ThreadFunction(void* context)

Then, where you are currently calling pthread_create, instantiate a Context object with "this" and the number (1 through 3).

Pass ThreadFunction as the start_routine, and the Context object as the last arg. Then, within ThreadFunction, you can call

Context* ctxt = (Context*) arg; ctxt->theObject->thread_work(ctxt->num)

Erix
  • 7,059
  • 2
  • 35
  • 61