Skip to main content
GameDev.net gamedev.net
🔒 Locked

Sharing object between 2 threads

Started by floatingwoods Jun 29, 2016 at 12:03 PM 14 replies 4.7k views
Original Post
floatingwoods
floatingwoods

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!

j_uk_dev
j_uk_dev

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 ;) ).

Alberth
Alberth

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.

Scourage
Scourage

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...
SeanMiddleditch
SeanMiddleditch
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!
phil_t
phil_t

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."
floatingwoods
floatingwoods

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).

BeerNutts
BeerNutts

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)
phil_t
phil_t

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.

SeanMiddleditch
SeanMiddleditch

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!
floatingwoods
floatingwoods

I need option 3 in what you mention.

My problem is not only the rendering, but also all the GUI.

So I have:

Game logic thread: runs the game and modifies the resources.

GUI thread: handles the GUI events and also renders the views.

I want the game logic to be compilable in headless mode, and optionally have a plugin with the GUI and rendering. Think of it as a simulation of some physical process:

a) we can have the simulation configured and modified via a GUI, and also visualized. This situation requires the headless mode game logic executable, and the GUI/rendering plugin loaded

b) But other times, we simply want to be able to run, say, 10 simulations in parallel, in headless mode: in that case, we are only interested in the data the simulation produces. This situation only requires the headless mode game logic executable

So all the dependencies to the GUI and OpenGl should be in the plugin, so that the game logic can also easily run on machines that do not have any display (i.e. no OpenGl or graphic card installed)

So I agree that duplicating the resources is a tremendous amount of work, and many things can go wrong if not done properly. So, let's for now forget the resource duplication, and consider that we have only one thread doing everything.

What I currently have is following:


class CObject_A
{ 
    CObject_A(); 
    virtual ~CObject_A(); 

    void doCalculationAndModifyObject(); 
    void renderObject() 

    CData* data; 
};

I basically currently have everything in one class (actually many classes, but for simplification sake). Because of that I cannot compile the gamelogic in headless mode (above class has the dependencies to the GUI/OpenGl). I need to separate the above class in 2 separately compilable entities, with least effort.

For that reason I mentioned the possibility to simply cast a class to another class, if the member variables are the same.

So I want to be able to do following:


// gameLogic executable:
CObject_A* object=new CObject_A();
object->doCalculationAndModifyObject();
callPluginRoutine(object);

And in my plugin:
void callPluginRoutine(CObject_A* obj)
{
    obj->renderObject();
}

If I want to be able to compile my gameLogic executable in headless mode, I am not allowed to have routine "renderObject()" in class CObject_A during compilation.

phil_t
phil_t

I think you're falling into the trap of "everything must be an object, and all functionality for manipulating the object must be in methods on the object".

You have:

a) the simulation data

b) the code that manipulates the data as it runs the simulation

c) the code that renders the data

There's no reason these all need to be the same object... especially given that you want the possibility of a + b without any dependency on c. b and c don't need to know about each other.

floatingwoods
floatingwoods

Phil, perfectly agree with you. If I had to rewrite the whole project, that's how I would do it. Unfortunately I have to modify an existing code (written by another person) that has several hundreds of thousands lines. It would take several months to do that. On the other hand, if I have a solution (maybe not that elegant) that works, and that can slowly be refactored while keeping current working solution, it would be great.

With the initial idea of simply casting an object of class A to and object of class B (where the two classes are identical except for the member functions), I could put a few ifdefs in the class definitions, I could compile class A in item A, and class B in item B, but using the common, existing class AB.

j_uk_dev
j_uk_dev

@floatingwoods still I'm not sure if I understand what you are trying to achieve but from what I see, you need exactly what @phil_t described ( correct me if I'm wrong, but this looks to me like typical MVC pattern ). From your description you need two interfaces to the same set of data. There is no real reason why those interfaces must duplicate the data.

With the initial idea of simply casting an object of class A to and object of class B (where the two classes are identical except for the member functions), I could put a few ifdefs in the class definitions, I could compile class A in item A, and class B in item B, but using the common, existing class AB.

This will work only if you don't use virtual functions and maintain the structural integrity, but I don't see why you can't create classes A and B for which a memebr of class C ( probably pointer ) would point at the shared data? Also I'm not sure what you call as a plugin? Are you planning to compile part of the code as shared library or something?

I think you're taking wrong direction here. Why in the headless mode simply render() function would not do anything? This is pretty common practice in game engines where they have to do some work without really rendering anything. You link against plugin library which either supports rendering or not. I did something similar long time ago with one of my old engines to be able to run only in serialisation mode. So it was compiled against "dummy" renderer. If you want more flexibility, place your plugin in the shared library and link it in the runtime by opening it and grabbing symbols. In that case you could determine if you support rendering but simply checking if the symbol exists.

In your idea you are sacrificing the binary compatibility and make it harder to maintain. But as I say, I'm not sure what you're really trying to achieve :) If I wanted to be able to process/render my data with plugin, then I'd go with proper interface through which I can access my data and move functionality to the library ( shared if possible ). Both, plugin and main application would need to be compiled against common header that defines data structure, so if you create ObjectA in main application and then you give ObjectA to the plugin function, the plugin will be aware of the data structure. Also not everything has to be class. Consider that member functions are in fact normal functions which take pointer to the 'this' object as first argument ( in a simple view of things ;) ). Which means, instead of creating ClassA::UpdateSimulation(), ClassB::Render() you would create for example in your plugin code:


#include <your-common-data-header.h>

extern "C" void RenderImpl( Data* pData )
{
  // do render your data here or leave empty
}

Your main application would look for a function pointer from the plugin and call it if it exists or ignore. It's just very simple example of decoupling plugin and main application. But as I said, I'm not sure if I understand what you're trying to do ;)

Edit: I forgot to mention that the plugin could be also responsible for spawning own threads if necessary, so when you run application in headless mode, there are no needless threads floating around. This is how I implemented OpenGL backend for my latest project, which requires to have a main thread for the command queue, while Vulkan plugin doesn't need to create one as command queue there works hmm... "out of the box" ;) But interface between an application and Vulkan, OpenGL and DX12 is exactly the same, they are just different in terms of the way they do things inside the shared library. For debugging I also have a dummy plugin ( which is used when running test cases on build machine - so this is sort of headless mode ;) ).

SeanMiddleditch
SeanMiddleditch

I want the game logic to be compilable in headless mode, and optionally have a plugin with the GUI and rendering.


Neither of which require threads in the first place. Why are you using threads?

I basically currently have everything in one class (actually many classes, but for simplification sake). Because of that I cannot compile the gamelogic in headless mode


Since you said that this is a large existing codebase and since you mention a separate compilation pass being viable for headless mode, you have the option of #ifdef's. They're a bad solution in most cases, but arguably a far better solution than some of the other things you're trying here.

For that reason I mentioned the possibility to simply cast a class to another class, if the member variables are the same.


No. No, no. No.

There are a billion and one legal and proper ways to solve your problem. Casting objects between different types is not one them. Drop the idea. It's wrong, there is _NEVER_ a reason to do, and it's wrong.

No.

The C++ standard actually allows a compiler to ignore your casts and generate code that does not actually do what you think, because casting between types is illegal and assumed to never happen in legal code. Older projects that chose to ignore the type aliasing rules just because the code happened to work in older simplistic compilers still have to disable various optimizations in modern compilers that rely on programmers not being rules-breaking miscreants.

Get off the idea. It's wrong. Do not do it. The end.

And in my plugin:
void callPluginRoutine(CObject_A* obj)
{
obj->renderObject();
}


So you want virtual functions, interfaces, components, static linker composition, templates, #ifdef's, or even just separate functions.

For example: move renderObject out into your plugin as a free-standing function. Then replace obj->renderObject() with renderObject(obj). Decouple your objects' tangled concerns.

Another example: keep renderObject in your class but just don't implement it in your generic code. Implement it only in your plugin. Member functions only need to be linkable/implemented if they're used by a TU.

There are many more options available here.
Sean Middleditch – Game Systems Engineer – Join my team!
floatingwoods
floatingwoods

Well, all this makes perfect sense. It seems I got scared (and mixed-up my thoughts) because of the thread, separate entities (exe + dll) and existing code aspect.

The fact that I need different threads is that my game logic already works using several dozens of threads (but perfectly synchronized). So it only makes sense to have a separate thread for the GUI and rendering (e.g. Qt also requires to run the main Qt code via the application main thread)

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.