Thread-safe question

Started by paulscode, March 23, 2008, 10:44:56 PM

Previous topic - Next topic

paulscode

This is a basic question more related to Java, I think, than jPCT.  I am rather new to threads, so I want to make sure I using good programming technique.  My question is: Is it is safe to call the following methods from a seperate thread (I am checking if the objects are null immediately before calling the methods):

Camera:
    getPosition()
    getDirection()
    getYAxis()
Object3D:
    getTransformedCenter()

I suppose it could be possible that the instance in question could have been changed or deleted during the nanosecond between when the "if" statement checks if the object is null and when one of these methods gets called.  How likely is that scenario, and other than that, is it likely that calling any of these methods from a seperate thread might lead to other exceptions/errors?

EgonOlsen

The thing it not so much about getting a nulled object accidently, but about getting garbage results if another thread is currently working on the object.
Example: Thread 1 is in the process of rotating the object, i.e. it modifies the rotation matrix. Before doing so, the matrix is a valid rotation matrix, and it also is one afterwards. But in between, in can be anything from a valid matrix to complete rubbish. Therefor, calling getTransformedCenter() from inside another thread can cause problems. It may return a correct center, but if it interferes with thread 1 working on the matrix, the result can be anything. That's basically the problem...you can avoid it be synchronizing the parts in question, but that may not be worth the afford. It's better to do all jPCT related stuff in one thread if possible. If that is not possible (for example when working in a client-server-environment), it may be a good idea to double buffer some things. the "advanced example" does this on the clients. The client's thread that polls the server may get new coordinates for remote objects while another thread renders the scene. To decouple them, the first thread doesn't manipulate the objects directly, but a shadow copy of the rotation and translation matrix, which will be transfered into the real ones after the drawing has finished. Of course, you still have to synchronize these operations (i.e. storing in the shadow copy and transfering into the real one), but at least you draw a clean state all the time without doing any synchronization inside the drawing itself.

raft

Quote from: paulscode
..instance in question could have been changed or deleted during the nanosecond..
that period is not necessarily that small (nanosecond). on a single core-cpu machine, threads are executed sequentially, so it is possible that after your first line of code is executed your thread is suspended for some time and some other thread's say 100 lines of code is executed

Quote from: paulscode
How likely is that scenario
as a rule of thumb about multi-threading, if there is even a minimal chance that something can occur, that thing will eventually occur

EgonOlsen

Quote from: raft on March 23, 2008, 11:05:10 PM
as a rule of thumb about multi-threading, if there is even a minimal chance that something can occur, that thing will eventually occur
So true! And i may add: I can be damn hard to debug this... ;)

raft

Quote from: EgonOlsen on March 23, 2008, 11:07:11 PM
So true! And i may add: I can be damn hard to debug this... ;)
absolutely..

paulscode

Awesome, thanks for the tips.

paulscode

I had another thought.  If the main issue is receiving garbage data, it should be possible somehow to detect when this occurs and try again.  The first thing that comes to mind is:

SimpleVector a,b;
do
{
    a = new SimpleVector( myObject.getTransformedCenter() );
    b = new SimpleVector( myObject.getTransformedCenter() );
}while( !a.equals( b ) );


    If a change occured in the middle of one of the calls to getTransformedCenter() and garbage results were received, then the two results would not be equal.  There is probably an error in my logic, but it seems reasonable  ;D

    Also, I was wondering about the double-buffer concept you mentioned.  Even if you were getting the data from a seperate buffer, wouldn't it still be possible that the seperate buffer was getting updated during the attempt to retrieve it's contents, and thus you would get garbage results?  Of course, in the example you indicated that you are queueing changes to be made after rendering is complete (I haven't looked at the example, but I assume the shadow copy is getting "locked" until you are finished changing it, so the other thread doesn't try and read from it while it is being changed).  Unfortunately I can't use a locking mechanism in my application, because this would be for the SoundManager, and I can't require users to make changes to their programs.

    My concern is that, the way SoundManager works now, the user calls the "tick()" method every frame from the rendering thread, but this could result in lag if the user creates many sources that must all be looped through each frame (ex. when the camera is translating - all sources must be re-sorted by distance from listener).

    I suppose I could design the class so it breaks the work down into smaller chunks if there are too many sources, to essentially emulate what happens when using a seperate thread.

    OR WAIT - I just had a thought -- I could calculate only the source distances in the tick() method, and store them in a second buffer (and I could lock elements while they are being updated).  THEN I do the sorting and culling on a seperate thread...   That might just work!

    Anyway, thanks for the discussion -- given me a lot to think about.

EgonOlsen

Quote from: paulscode on March 24, 2008, 04:09:41 AM
SimpleVector a,b;
do
{
    a = new SimpleVector( myObject.getTransformedCenter() );
    b = new SimpleVector( myObject.getTransformedCenter() );
}while( !a.equals( b ) );


    If a change occured in the middle of one of the calls to getTransformedCenter() and garbage results were received, then the two results would not be equal.  There is probably an error in my logic, but it seems reasonable  ;D
Bad idea...two times the same garbage is still garbage. When two thread work on the same data, you always have to synchronize. The trick is to minimize it when possible to avoid bottle necks as much as possible.

Your SoundManager idea sounds good to me (assuming that it's really needed to do this in a thread...i still don't think it really is...), as long as the worker thread (the one that does the sorting and culling) remains active over the life time of the application. Because the overhead of spawning a new thread every frame won't be bearable.

paulscode

Yes, if I were to use this, it would be a single thread.  First though, I am going to run "worst case" with a few thousand sources to see if it is necessary.  I suspect sorting a huge list every frame will result in major lag, but I will see.  The drawback to not having pointers in Java is I can't just loop through the list a single time, it will require some kind of recursion to put the sounds in order.  I could reduce the overhead a little by stopping once I have sorted the "max source count" sources, since all others will be culled.  I'll just have to see what effect this has on performance.