Advertisement

How do you deal with shared data and work load balancing?

Started by July 09, 2016 08:01 PM
4 comments, last by Acar 8 years, 4 months ago

Hello everyone. I'm currently using IOCP with threads to handle both networking and message processing. Since I do not have a fixed main loop I'm creating a thread whenever I need some system updated periodically.

Due to that, I'm using locks to synchronize entirety of the shared data(which is the majority of the data since it's a game server). Though, I've been told, this could result in a slower overall performance than a single threaded application and a higher chance of ending up with deadlocks.

I would like to get rid of majority of these locks by changing my message processing and game updating design. I'm not experienced enough to know possible message processing/game loop designs which lead me to creating this kind of awful system in the first place. That is why I would very much appreciate if someone could explain me how it's done in industry.

Tasks which the server is responsible with(Pretty much what any MMORPG server would need to do):

- Keeping track of players surroundings and updating them properly(Currently using a fixed node system and a thread for updating.)

- Handling user specific data(Such as items, skills, etc.)

- Combat systems(Casting skills, normal attacks, etc.)

- Handling global data(Guilds, auction, etc.)

- Other small/big systems such as trading, party, other MMORPG elements.

I'm creating a thread whenever I need some system updated periodically.


You'd likely want to create a few threads (as many as there are cores) up front, and then "post" work to do to those threads, rather than creating new threads, or thread-per-subsystem.
That way, "work to do" is always well described as a work record (you're forced to structure it this way) which means you can easily-ish scale the number of threads between one and N.

Separately, do you actually need multiple threads?
Have you written a networked game before?
Did it run out of CPU in a single thread?
If so, where did the profiler say that you were spending your time?

In general, before you can build a "correctly" threaded application, you need to have some idea of where the problems will be.
For example: Do you have a lot of collision pairs to test in physics? Those could easily be parallelized.
Do you have a lot of database/file data to look up for each action request? Then parallelizing isn't the solution; asynchronizing is.
Without knowing exactly what your problem is, you can't possibly hope to effectively address it!
(And you'll have even lower chance of getting good advice from the internet if you can't describe it ...)

So, typically, the main problems that need CPU on the server are related to physics/entity simulation. Let's assume that's what you're going for.
In general, to make a multi-threaded game update engine, you need to structure all computation to be double-buffered.
During a particular time step, all entities/objects/systems are allowed to ONLY READ from the "current" copy, and ONLY WRITE to the "next" copy.
As long as you won't have multiple threads wanting to write to the same output state/copy at the same time, you will then not need locks.
Once the update has completed, you will have a short time of single-threaded-ness where the "current" and "next" states are swapped.
This may be as short as flipping an "current index" global variable between the values 0 and 1!

Separately, you will be generating messages to send to other objects/nodes/whatever.
You could use a lock-free FIFO for those messages.
Make it big enough that you will never generate more messages in a single step than there is space in the FIFO.
That way, you won't end up dropping any messages in the lockless phase.
enum Bool { True, False, FileNotFound };
Advertisement

See if you can handle some of the operations as seperate staged/phased data waves(?) within the server with seperate core-threads working each 'phase' in a pipeline fashion.

Thats to allow use of coarse locks on large chunks of data, which get handed off to the next phase en masse (With minimal lock manipulations)

That is independant groupings of processings like network processing(client sessions) and decoding inbound commands, actioning and arbitrating game mechanics events, outbound data to clients, etc... With the 'turns' pipelining (internet delays mean the game probably can be processed in steps without affecting perceived timing). When each thread finishes wuth its turn data (and is waiting for its next turn to start) it might do some secondary less time dependant tasks as filler...

Note that this frequently does require some data replication and buffering between the pipelined phases to keep them independant (the data that phases processing is taken in, integrated, and then what its producing marshalled to be passed on to the next phase). Some heavy phase processing might be run on more than one core with the inbound data read-locked and outbound data designed to be inedpendant (just gets queued up for the next step down the pipeline.

-

This kind of thing is more used in Clients which may be doing prep work 2/3 rendering frames ahead (with seperate core-threads working in parallel) and the handoffs of phase completion and with tasks broken up to try to keep all the cores as busy doing useful work as much as possible.

--------------------------------------------[size="1"]Ratings are Opinion, not Fact

Thanks for the answers.

I'm creating a thread whenever I need some system updated periodically.


Separately, do you actually need multiple threads?
Have you written a networked game before?
Did it run out of CPU in a single thread?
If so, where did the profiler say that you were spending your time?

- That's something which I didn't ask myself when I started writing my server. Should have.

- Not necessarily. This was initially way out of my league, though, I've been working on it for a considerably long time and learnt a lot along the way.

- Again, it's always been multi-threaded.

One of the recent thoughts I had was using IOCP threads only for networking and using a single consumer work queue for all other jobs. Then I could create new single consumer queues for tasks which can be run independantly, if it seemed necessary.

That is independant groupings of processings like network processing(client sessions) and decoding inbound commands, actioning and arbitrating game mechanics events, outbound data to clients, etc... With the 'turns' pipelining (internet delays mean the game probably can be processed in steps without affecting perceived timing). When each thread finishes wuth its turn data (and is waiting for its next turn to start) it might do some secondary less time dependant tasks as filler...

I do like the idea very much, though, it might be too difficult to group the systems I have since they often require information from other systems during processing.

Tasks which the server is responsible with(Pretty much what any MMORPG server would need to do):
- Keeping track of players surroundings and updating them properly(Currently using a fixed node system and a thread for updating.)
- Handling user specific data(Such as items, skills, etc.)
- Combat systems(Casting skills, normal attacks, etc.)
- Handling global data(Guilds, auction, etc.)
- Other small/big systems such as trading, party, other MMORPG elements.

The way I see it, these are all pretty much "game logic" which is a good candidate for all going in one single thread for sequential processing. Firstly, they're heavily interdependent (as you know), and secondly, they're mostly trivial in terms of CPU demands. As such, they're not the sort of things you normally want on multiple threads.

If you just need certain things updating periodically, threads are the wrong choice. Just update each system when necessary based on timesteps as you would in a single-player game.

What I like to push out to other threads (or processes, preferably) are network reads/writes and buffer handling (if necessary - the OS covers a lot of this for you automatically), and AI (eg. A* pathfinding, influence map updates, decision making). But even AI is tricky to manage since it usually needs to read the world and character state. One way I've dealt with this is for state changes to all be done by update messages, meaning the AI thread/process can have its own copy of the world state based on the messages it receives. But it does require handling your game updates in a careful way, one that doesn't fit well with the traditional "anything can modify anything" approach most games still use.

Thanks for the answer. I couldn't agree more. Having whole game logic run in multiple threads is adding nothing but complication due to synchronization at the moment.

What I like to push out to other threads (or processes, preferably) are network reads/writes and buffer handling (if necessary - the OS covers a lot of this for you automatically), and AI (eg. A* pathfinding, influence map updates, decision making). But even AI is tricky to manage since it usually needs to read the world and character state. One way I've dealt with this is for state changes to all be done by update messages, meaning the AI thread/process can have its own copy of the world state based on the messages it receives. But it does require handling your game updates in a careful way, one that doesn't fit well with the traditional "anything can modify anything" approach most games still use.

I'm still using IOCP threads for networking. Which includes receiving/sending/deserializing network data.I will first adjust my game systems to single threaded update approach. Then if it appears to be a performance issue, will try to implement solutions which you guys suggested.

This topic is closed to new replies.

Advertisement