This topic is 2499 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

This is going to sound stupid, but I just need someone to clarify something, because I'm having a weird problem and can't work out why.

Question: Variable Addresses are valid between 2 different threads of the same process, correct?

So I create a global variable in Thread 1, and pass it's Address to Thread 2 - Thread 2 will be able to access it. Right?

Cheers.

Share on other sites
Yes. The most common problems are often because of lacking synchronization, which can cause the perceived value at the address to differ from the actual value in some threads.

Share on other sites
Well this is the weird thing, I've implemented Critical Sections to protect and places where data may be accessed by the 2 threads.

I pass my Window classes pointer to my rendering thread, from the logic Thread. And this seems to work. However, the Window class includes a pointer to a Material Class. And this is what gets invalidated. The Window's data members are intact, but drilling down into the Material data it returns mush - and the pointer value appears to have changed.

Share on other sites
Is the pointer correct before you create the second thread or pass it the window pointer?
If you update the pointer later the other thread might already have cached the value in a register. Make sure you use critical sections everywhere you access a value, and make shared variables volatile.

Also, if you cast your pointers when passing them between threads, make sure you don't break a pointer to a class with virtuals or inheritance, as for example casting a class pointer to a void pointer might yield a different value than the original pointer, which must be properly cast back to restore the correct value.

Share on other sites
Yes the pointer is correct before I pass it to the second thread. Once added to the rendered, pointers are never altered, or modified. The class becomes purely Read Only.

Also, no casting of pointers occurs anyway in the engine.

This is how it works at the moment:

- Material Class declared Global
- Window Class declared Global
- Initialises Graphics Renderer (Starting Thread 2)
- Material pointer is passed to Window Class
- Various Window properties set (size, scale, position, etc)
- Pointer to Window is passed to Renderer Thread, where it is placed into a std::vector<Window*>, by means of a push back.

- The renderer thread then iterates through the Window Vector, and renders accordingly. And Window's added by the Renderer thread itself (ie. Debug Window) render fine. Windows passed by Thread 1 have corrupted Material Pointers - but the rest of the Window Data is intact.

Cheers

Share on other sites

Well this is the weird thing, I've implemented Critical Sections to protect and places where data may be accessed by the 2 threads.

I pass my Window classes pointer to my rendering thread, from the logic Thread. And this seems to work. However, the Window class includes a pointer to a Material Class. And this is what gets invalidated. The Window's data members are intact, but drilling down into the Material data it returns mush - and the pointer value appears to have changed.

Anything that both threads read has to be synchronized. If you "pass a pointer" then you need locks around that pointer. You can use some atomic operations like CAS to do some specific stuff between threads, but in general you need locks.
The issue is, that if you have:
Window *foo = new window;
foo->material->color = black;

None of those are atomic operations, so it could really be:
Thread 1: Window *foo = allocate( sizeof( window ) );

This counts for ANY line of C code. It isn't atomic. Multiple lines of C code aren't atomic either, so the compiler is free to rearrange them from what your code says as long as it does the same thing in a single threaded world. Thus, no re-arrangement of code will fix the threading issue. The only way to fix it is to lock around everything you share.

Share on other sites
So you're saying that each of my classes should have a Lock/Unlock function, and before any thread does *ANYTHING* with that class, it must call the Lock function, and then the Unlock function after it's done with it.

Would that solve the problem?

Share on other sites

So you're saying that each of my classes should have a Lock/Unlock function, and before any thread does *ANYTHING* with that class, it must call the Lock function, and then the Unlock function after it's done with it.

Would that solve the problem?

No, you don't need locks in the class, unless the class is used on both threads at the same time. If you are just passing the material from one thread (loader) to another (render), then you only need to lock around the point where you pass off the data. What you;d then need is a lock around the window_vector.push_back( window * ) and functions where window_vector[ index ] is used. The OS locking functions will place barriers, so that by the time the window_vector contains your pointer, all operations previous to this will be completed and pushed out to memory where the other thread can read it.

Share on other sites
That's what I've got implemented - I have a CS around the window_vector.push_back() code, and then another CS around the portion of code that actually tries to render that window.

Example:

 Lock(csWindows); window_vector.push_back(someWindowAddress); Release(csWindows); 

In the Render routine:
 Lock(csWindows) for(int i = 0; i < window_vector.size(); ++i) { window_vector.Render(); } Release(csWindows); ;

Share on other sites
Multithreading bugs are typically hard to locate. Without a full code dump, it's hard for anyone to speculate as to any more issues you are having with it. One thing you can do is set a hardware breakpoint on the variable in question. on the line where you first set it. The, when something modifies it, you'll be able to see what. If anything is modifying things outside your locks, you'll be able to see it.

Share on other sites
You should declare things that will be used by multiple threads as volatile. When you want to use the object, lock as normal and then create a non-volatile reference to the volatile object using const_cast, and make your changes through that reference.

As a bonus, because most volatile objects will generate compile errors when you try to use them, this will help you catch some threading related bugs.

To make life easier, you can make a templated class that acts as a lock/smart pointer. Its constructor takes a mutex/critical section object and a volatile object, locks the mutex/cs and initializes a non-volitile reference to the object you wanted to modify. During its lifetime it acts as like a smart pointer to the volatile objects by overloading operator * and operator ->. In its destructor unlock the mutex/cs. Make its assignment operator and copy constructor private to prevent accidents.

As a bonus, you don't need to worry about unlocking the mutex/cs if an exception gets thrown.

Share on other sites
I've somehow managed to get it working - now whether that's by fluke or design I'm not sure. I must've have some out-of-date code, and a full rebuild finally include some of my CS stuff.

Thanks for all the help guys.

@smart_idiot: That doesn't sound like too bad an idea. Might give it a try and see what performance is like.

Share on other sites
Out of curiosity, what multiprocessing books have you read, what is your level of programming experience, and what multiprocessing experience do you have?

Parallel processing and multiprocessing is not an easy topic. It is not something you can easily pick up from the reference documentation and a few poorly-written online tutorials.

If you are trying to jump into multiprocessing (such as threads) without enough background, you will be facing an endless procession of bugs on your own personal death march.

It is hard enough to write a program and find the bugs when everything is happening in the order you specify. When you throw concurrency issues into the mix the bug difficulty increases by an incredible amount. It is quite difficult to debug cases when your data gets corrupted in the middle of using it. You can have values modified mid-copy. You can have values be set and immediately modified to something else. You can have seemingly innocent statements cause a ripple effect of locks and resource contention that not only slow your specific process, but slow the entire OS and every programming running on it. Tiny bugs will accidentally introduce resource starvation and deadlock and other problems that only get triggered on seemingly random ways yet can never be seen in your debugger because of the way a debugger works.

Multiprocessing is generally not a good topic for anyone in "For Beginners"

Share on other sites

Parallel processing and multiprocessing is not an easy topic. It is not something you can easily pick up from the reference documentation and a few poorly-written online tutorials.

If you are trying to jump into multiprocessing (such as threads) without enough background, you will be facing an endless procession of bugs on your own personal death march.

I'd like to add that when you are supposed to have enough background, it's still a pain in the ass.

Today newbies have a big advantage, though. In my times we didn't have more than one processing core and there were bugs that seemed to work perfectly in a computer, and then you upgraded the CPU or the RAM or tried it in a faster or slower computer, or just changed whatever and BAM! It began crashing every other time you ran it.

Nowadays it's fairly common to have a quad core, and bugs are quite more consistent with one of those :-)

Share on other sites

[quote name='frob' timestamp='1302910644' post='4798969']
Parallel processing and multiprocessing is not an easy topic. It is not something you can easily pick up from the reference documentation and a few poorly-written online tutorials.

If you are trying to jump into multiprocessing (such as threads) without enough background, you will be facing an endless procession of bugs on your own personal death march.

I'd like to add that when you are supposed to have enough background, it's still a pain in the ass.
[/quote]

Having the requisite background knowledge reduces it from "death march" to "painful but educational", and with enough experience it becomes "mostly smooth as long as everyone follows the rules, and fun challenges with moments of moderate pain for those who broke those rules."

Both multiprocessing and network programming are common causes of programmer hair loss. Don't enter until you are ready.

Today newbies have a big advantage, though. In my times we didn't have more than one processing core and there were bugs that seemed to work perfectly in a computer, and then you upgraded the CPU or the RAM or tried it in a faster or slower computer, or just changed whatever and BAM! It began crashing every other time you ran it.

Nowadays it's fairly common to have a quad core, and bugs are quite more consistent with one of those :-)
[/quote]

More consistent, yes. But not necessarily in a way conductive to positive mental health.

Rather than giving you brief respites with the false impression that everything works, you are more likely to instead have continuous knowledge of the awful state of things.

Often the bliss of not knowing is an important part of maintaining sanity.

Share on other sites
I would say that I'm Intermediate - Advanced at programming, but this is my first true venture into Multithreading.

The only reason I posted in For Beginners is because historically I've gotten much better help here than General Programming.

Share on other sites

I would say that I'm Intermediate - Advanced at programming, but this is my first true venture into Multithreading.

Then, as frob implyed, you probably would like to read a serious book on the subject. I fear I can't recommend any specialized literature as the only one I know of is written in spanish and there are no translations that I'm aware of -and it's not so great anyway.

However, any book on Operative Systems should have a basic (but formal) coverage of the subject and should be an interesting reading. Operating systems concepts by Silberschatz and Galvin has a pretty neat cover and Modern Operative Systems by Andrew Tanenbaum is regarded as "The book" on OS.

Edit: When I wrote "A pretty neat cover" I didn't mean a good coverage of the subject; I have not read it but should be OK. I meant just that.

Share on other sites
@ravengangrel:

Thanks for the recommendations. I'll check them out.

Share on other sites
That cover is quite possibly one of the most tragic things I've ever seen....

Share on other sites

Edit: When I wrote "A pretty neat cover" I didn't mean a good coverage of the subject; I have not read it but should be OK. I meant just that.

WTF?

Is drug usage a common problem amongst programmers then?

Share on other sites

[quote name='ravengangrel' timestamp='1302945570' post='4799076']
Edit: When I wrote "A pretty neat cover" I didn't mean a good coverage of the subject; I have not read it but should be OK. I meant just that.

WTF?

Is drug usage a common problem amongst programmers then?
[/quote]

Nah, my mom made me this way :-)

Share on other sites

Nah, my mom made me this way :-)

Lol I was referring to whatever drug induced stupor lead someone to draw that cover!

Share on other sites

You should declare things that will be used by multiple threads as volatile. When you want to use the object, lock as normal and then create a non-volatile reference to the volatile object using const_cast, and make your changes through that reference.

Besides for your "as a bonus", why would you do this?
Locks already create the necessary memory barriers, so the variable doesn't need to be volatile. Without a lock, a volatile variable is not safe to use anyway, since volatile does not make the variable atomic, so you have to use a lock. So what does volatile buy you here (besides the potential compiler warnings when misusing the variable, which of course can be useful).

Then, as frob implyed, you probably would like to read a serious book on the subject. I fear I can't recommend any specialized literature as the only one I know of is written in spanish and there are no translations that I'm aware of -and it's not so great anyway.

I would recommend The Art of Multiprocessor Programming. I am currently reading Patterns for Parallel Programming and so far it seems to be quite good (covers various parallel programming patterns and contains examples using OpenMP, MPI and also Java's concurrency library).
Finally, for real-world multithreading (rather than multithreading for the learning experience), I would recommend not doing it yourself (and using a task model instead of a thread model) and using Intels Threading Building Blocks instead - in which case, I would recommend this book.

Share on other sites
Besides for your "as a bonus", why would you do this?
Locks already create the necessary memory barriers, so the variable doesn't need to be volatile. Without a lock, a volatile variable is not safe to use anyway, since volatile does not make the variable atomic, so you have to use a lock. So what does volatile buy you here (besides the potential compiler warnings when misusing the variable, which of course can be useful).

It is my understanding that any data which can be altered in ways unknown to the compiler should be marked as volatile, and that memory barriers are completely independent of the assumptions an optimizing compiler might make about your data. If I am wrong, then I apologize.

Share on other sites

It is my understanding that any data which can be altered in ways unknown to the compiler should be marked as volatile, and that memory barriers are completely independent of the assumptions an optimizing compiler might make about your data. If I am wrong, then I apologize.

No need to apologize!

Also, quote from this paper:

[color="#333333"]
Well, Lock comes from a threading library, so we can assume it either dictates enough restrictions in its specification or embeds enough magic in its implementation to work without needing volatile.
This is the case with all threading libraries that we know of. In essence, use of entities (e.g., objects, functions, etc.) from threading libraries leads to the imposition of "hard sequence points" in a program-sequence points that apply to all threads.[/quote]