Sharing object between 2 threads

Started by
14 comments, last by floatingwoods 7 years, 9 months ago

Hello,

I have following problem:

I have an object that belongs to a main thread, and that I want to share with an auxiliary thread. The main thread does calculation on the object and modifies it, and the auxiliary thread simply reads and visualizes that object.

I want the auxiliary thread to be able to:

1. Directly read the main thread object. In that case, the main thread needs to be locked during that time.

2. Read a copy of the main thread object. In that case, the main thread does not need to be locked during that time.

Depending on the user settings, I want to have situation 1 or 2 described above.

Imagine the main thread object to be following class:


class CObject_A
{ // the main thread creates/destroys and uses this object
    CObject_A();
    virtual ~CObject_A();

    void doCalculationAndModifyObject();
    CObject_A* duplicateObject();

    int data1;
    int data2;
};

And imagine the auxiliary thread to have following class declaration:


class CObject_B
{ // the auxiliary thread uses this class. But it does not create/destroy the object.
    CObject_B();
    virtual ~CObject_B();

    void displayObject();

    int data1;
    int data2;
};

So, is following legal if I do this in the auxiliary thread:


CData_A* objectCreatedFromTheMainThread=getObjectFromMainThread();
CData_B* objectForTheAuxiliaryThread=(CData_B*)objectCreatedFromTheMainThread;
objectForTheAuxiliaryThread->displayObject();

The question is basically: given two different classes that have exactly the same data definition (including order and same compiler), but that have different member functions, is it legal to cast the object from one class to the other?

Thanks for any insight!

Advertisement

You may try to cast them if they are POD structures and you know they are binary compatible. If you define member functions - still you can do it unless you have any virtual function. Then casting won't be legal. Both objects use different vtables. C/C++ won't stop you from shooting your foot and you can do lot of things, but unless what you want to do is really justified ( and you will make sure, the objects are binary compatible ) it is asking for trouble ( and being shot ;) ).

I would make a class for the shared object (immediately giving you a good spot for adding the lock), and two classes taking a reference or pointer to the shared object, implementing each interface.

It sounds like your main thread is running the simulation and your auxiliary thread is rendering the entities. You can probably get away with reading the entity without locking if you make sure that you only read from it. You need to look at it from a memory contention standpoint. If you don't lock, you run the risk of having part of the data change on you while you are reading from it. It's up to you if that is a bad thing or not.

Double buffering the data may help prevent partial updates, but you need to prevent the buffers from being swapped while somebody is reading from it (again locking).

You might want to consider your threading approach. Having a separate render thread may not be the best thing in this case. You might want to have the main thread do an update pass then a render pass but use a worker thread pool to achieve parallelism. Instead of having a single "coarse grain" thread for rendering, use a bunch of work threads to get "fine grain" threading within a single phase.

For example: In your main game loop, you update your game objects by passing them all off to worker threads to be done in parallel. Then when they're all done, you go to the render phase. In your render phase, you do entity visibility culling and build render commands per entity in a bunch of worker threads in parallel. if you are able to encapsulate the render commands, you can build a list of them in the main thread and then pass the entire list off to a coarse grained rendering thread for execution while your main thread goes back to updating.

cheers,

Bob


[size="3"]Halfway down the trail to Hell...
What are you actually trying to do? We can answer your questions but there's never a good reason to do all the things you're asking, so there might be an even better answer for your real problem.

Always ask about what you're actually trying to achieve. XY Problem.

Sean Middleditch – Game Systems Engineer – Join my team!

The question is basically: given two different classes that have exactly the same data definition (including order and same compiler), but that have different member functions, is it legal to cast the object from one class to the other?

Thanks for any insight!

Before trying to do that crazy thing, I would instead either:
- have flat functions that operate on the data object, or
- still have your two objects, but bundle all the data into another struct, and that data can be shared by pointer or reference between the objects.
But more importantly, to quote from the page Sean linked:
"Others try to help user with Y, but are confused because Y seems like a strange problem to want to solve."

Thanks for all the good answers. I realize that the better approach would be to use the same data structure in both threads, while having different classes.

What I am actually trying to do is:

The main thread handles the game logic and works on the resources (reading/writing them). The auxiliary thread handles the display only, reading only from the resources.

I want at a later point to be able to either have the render thread:

1. either read from the original data while the game logic thread waits (thus avoiding copying the resources at all).

2. either read from a copy of the original data, while the game logic continues its work.

In case 2 above, copying the resources will be done as a light copy first (i.e. actually sharing the resource until the point where the main thread modifies it: in that case, the main thread will first deep-copy the resource before modifying it, while the render thread will still read from the old, unmodified resource, thus avoiding a crash).

Thanks for all the good answers. I realize that the better approach would be to use the same data structure in both threads, while having different classes.

What I am actually trying to do is:

The main thread handles the game logic and works on the resources (reading/writing them). The auxiliary thread handles the display only, reading only from the resources.

I want at a later point to be able to either have the render thread:

1. either read from the original data while the game logic thread waits (thus avoiding copying the resources at all).

2. either read from a copy of the original data, while the game logic continues its work.

In case 2 above, copying the resources will be done as a light copy first (i.e. actually sharing the resource until the point where the main thread modifies it: in that case, the main thread will first deep-copy the resource before modifying it, while the render thread will still read from the old, unmodified resource, thus avoiding a crash).

Why do you have a separate thread for Rendering? Is the Rendering phase a bottleneck in your program? What happens if you simply render all your object within the main game loop?

Basically, are you sure what you're doing is worth the trouble? Multithreaded programming can be difficult to do correctly, and if you miss 1 thing, you'll start having odd bugs that are very hard to debug. Be sure you really need to do it before trying to do it.

If it's as an exercise, then disregard, but if it's a solution to a bigger problem (like making a game), then I'd re-evaluate and ensure it's really needed.

FWIW, in my hobby games I've written, I've never seen the need for more than 1 thread. Some big games will need more, but they have to have good reasons for them.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

A few comments:

Is this a learning exercise, or do you already know for sure that you have a performance problem and will need this optimization? If not one of those two, I would say "why bother?".

Also, rendering from a background thread can be problematic with a lot of rendering APIs (you don't say what you're using), so make sure this will work for you.

What kinds of things are being modified on the resources? Things will be simpler if you can always have the rendering thread working on a copy of the data, e.g. copy it each frame. But really, you only need to copy the data that ever gets modified (e.g. transform, etc...). If you can organize your resources in such a way that the modifiable data for all of them is in one big block of memory, this can be as simple and quick as a memcpy, and then telling the render thread "ok, here is the data you should be looking at now".

"read from the original data while the game logic thread waits" -> As far as I'm concerned, if you're talking about having the main thread block while waiting for things, you're already going down the wrong path.

The main thread handles the game logic and works on the resources (reading/writing them). The auxiliary thread handles the display only, reading only from the resources.


What kind of resources? What needs to be shared?

You want to be _very_ wary of any sharing mechanism that ever locks. It can at it's worst result in a multi-thread system that's _slower_ than a single-thread system.

When two thread need the same data, you've got only three _good_ options:

1. The data is purely immutable. No locking or synchronization is required. Works for static level geometry and materials and the like, but may be a problem for dynamic items. Note that the immutability is only required while multiple threads are using the data, so if you kick off a render job and then await it's completion, you are free to modify the data outside of that window.
2. Copies of data are made before the multi-thread situation arises and changes are reconciled at the end. This results in a "shared-nothing" approach (each thread's computation set is isolated). Reconcilation can be difficult or even impossible if you don't constrain your problem space and design your data structures accordingly, though.
3. Synchronize one-way updates with a message system. In this approach, each thread has its own copy of the data, like in step 1. Only one of the threads has a mutable view of a given object. When the mutation is complete and the object is in a valid state, a copy of the object is "mailed" to the other thread. That thread can then update its copy of the object at its leisure (when it knows it's safe to do so). This requires a quality thread-safe queue of some kind.

You almost certainly want option 1 or 3. Likely a combination there-of (1. for objects that never change after startup, 3. for objects that change each frame).

In this approach with rendering, at the end of a game logic update you'd package up the game state relevant to rendering (which objects exist, their transforms, animation state, etc.) and dispatch that state to the render thread. The render thread will only read out of that queue at the beginning of a frame, because changing state mid-render is going to do horrible things (e.g. shadows not matching the position of the shadow casters or lights). This approach also allows the threads to run full speed without expensive synchronization; if the render thread is running extra fast it can use interpolation to provide smoother animations and if the game thread is running faster, the sole result will be a lower FPS. You'll probably want some minimal synchronization in case one thread is running _much_ faster than the other (there's no reason to run 100 game updates per frame and no reason to render 100 frames per update) but a little flexibility is good; it can help mask frame time spikes in one thread or the other, among other things.

Sean Middleditch – Game Systems Engineer – Join my team!

This topic is closed to new replies.

Advertisement