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

Threads in multiplayer server

Started by D13t3r Aug 22, 2011 at 9:04 PM 9 replies 2.1k views
Original Post
D13t3r
D13t3r
Hello,

I'm currently coding my multiplayer server. Now my question here is how you use threads in multiplayer game servers?

I have different kind of messages like login, choose character, create character, move, engage, etc.
Do you usually have one thread/threadpool for each message type? Or do you usually use one thread/threadpool for related messages like login, choose character, create character?
How do most online game developers do this?

Oh, and I don't have to worry about synchronizing, race conditions, etc., because I'm planning to use the actor model for parallelism.
rip-off
rip-off
Why have you decided to use threads? Have you simulation data that proves that you cannot run a serial server with expected loads on your target hardware?
ApochPiQ
ApochPiQ

Oh, and I don't have to worry about synchronizing, race conditions, etc., because I'm planning to use the actor model for parallelism.



What makes you think this is true?


I would venture to guess that at this point you are not remotely experienced enough as a programmer to be writing a multithreaded game server. I suggest avoiding threading as much as possible, and doing what you can with a more manageable technique. Chances are you will never need to use threads at all for your game.

D13t3r
D13t3r

Why have you decided to use threads? Have you simulation data that proves that you cannot run a serial server with expected loads on your target hardware?


Nope, but since we live in a world with more and more cores every day, well you get the picture...


[quote name='D13t3r' timestamp='1314047092' post='4852506']
Oh, and I don't have to worry about synchronizing, race conditions, etc., because I'm planning to use the actor model for parallelism.



What makes you think this is true?


I would venture to guess that at this point you are not remotely experienced enough as a programmer to be writing a multithreaded game server. I suggest avoiding threading as much as possible, and doing what you can with a more manageable technique. Chances are you will never need to use threads at all for your game.


[/quote]

Because the actor model assures me that it is free of race conditions.

Anyway, what are the limitations of a single threaded server?
ApochPiQ
ApochPiQ
Erm... let me try again.

Who told you the actor model is free of race conditions? Why do you think this is true? Where did you get this information?


The real limitations of a single-threaded server are not relevant to your situation. You will never hit them.
D13t3r
D13t3r
Who guarantees me that? I guess the people who implemented the actor library/invented actors. No shared state, no race conditions.

What do you mean with "my situation"?
ApochPiQ
ApochPiQ
Race conditions have nothing to do with shared state. It is entirely possible to generate a race in an actor model; in fact, it's downright trivial. Ten minutes on Google can give you some splendid examples.


"Your situation" is that you are not working on World of Warcraft, or The Old Republic, or anything like it. You will never have enough users or traffic or throughput to truly max out what you can accomplish with a well-written single threaded server. So why go through the pain of multithreading?
Hodgman
Hodgman
Do you usually have one thread/threadpool for each message type? Or do you usually use one thread/threadpool for related messages like login, choose character, create character?How do most online game developers do this?
When your game starts up, you make one thread per hardware-thread, and distribute your work amongst this pool of workers the entire time the game is running.

To implement the actor model, you basically split your actors evenly between the threads, and have each actor execute all of the messages that is has received.
e.g. if you've got 4 threads and 100 actors, each thread is given a group of 25 actors to update (assuming no load balancing).

Race conditions have nothing to do with shared state. It is entirely possible to generate a race in an actor model; in fact, it's downright trivial. Ten minutes on Google can give you some splendid examples.
To elabore on this terse response --- below is a simple actor-model based system that demonstrates a race condition.
If two BarActor's are asked to damage a FooActor at the same time, one of the updates will be lost.
[source lang=cpp]class Actor
{
public:
void Update() { vector msgs = CopyAndClear(m_messages); foreach(m in msgs) m->Execute(this); }
protected:
void Enqueue( Message* m ) { m_messages.push_back(m); }
private:
thread_safe_vector m_messages;
};

class FooActor : Actor
{
public:
void RetrieveHealth( BarActor* p ) { Enqueue( new RetrieveHealthMsg(p) ); }
void SetHealth( int h ) { Enqueue( new SetHealthMsg(h) ); }

private:
class RetrieveHealthMsg : public Message { public:
RetrieveHealthMsg( BarActor* p ) : p(p) {} BarActor* p;
virtual void Execute(FooActor* self) { self->RetrieveHealth_(p); delete this; } };
class SetHealthMsg : public Message { public:
SetHealthMsg( int h ) : h(h) {} int h;
virtual void Execute(FooActor* self) { self->SetHealth_(h); delete this; } };
friend class RetrieveHealthMsg;
friend class SetHealthMsg;

void RetrieveHealth_( BarActor* p ) { p->OnRecieveHealth(this, m_health); }
void SetHealth_( int h ) { m_health = h; }
int m_health;
}

class BarActor : Actor
{
public:
void MassiveDamage(FooActor* foo)
{
foo->RetrieveHealth(this);
}
void OnRecieveHealth(FooActor* foo, int h)
{
Enqueue( new DoDamageToMsg(foo,h) );
}
private:
class DoDamageToMsg : public Message { public:
RetrieveHealthMsg( FooActor* p, int h ) : p(p),h(h) {} FooActor* p; int h;
virtual void Execute(BarActor* self) { self->DoDamageTo_(p,h); delete this; } };

void DoDamageTo_(FooActor* p, int h) { foo->SetHealth(h + m_damage); }

int m_damage;
};

/* Flow:
Pass #1
Execute -- BarActor::MassiveDamage
Enqueue -- FooActor::RetrieveHealth
Pass #2
Execute -- FooActor::RetrieveHealth -> BarActor::OnRecieveHealth
Enqueue -- BarActor::DoDamageTo
Pass #3
Execute -- BarActor::DoDamageTo
Enqueue -- FooActor::SetHealth
Pass #4
Execute -- FooActor::SetHealth
*/[/source]This particular demonstration is a bit silly of course -- [font="'Courier New"]FooActor[/font] should've just implemented a [font="'Courier New"]TakeDamage[/font] function, instead of a [font="'Courier New"]Set/GetHealth[/font] function, which would've avoided the race condition -- but the point is that you've still got to be aware of these kinds of mistakes when writing an actor-based system.


Now, it is entirely possible to implement a high-performance, deterministic, stable and scalable system based off the actor model... but it's difficult. I can say that because I've done it before, and it took me several months just on the low-level framework details (i.e. the [font="Courier New"]Actor[/font] class and related classes). To make it easier to use, I also had to implement futures and message arbitration systems.

Also note that the actor-model is thread-safe once implemented, but your actual implementation of the actor-model itself will need to make use of 'regular' shared memory programming. In my example, I've brushed over this detail by inventing a class, [font="Courier New"]thread_safe_vector[/font], which would have to provide the thread-safety guarantee of allowing multiple-writers and a single-reader concurrently. Also in my example above, I'm using [font="Courier New"]new[/font] and [font="Courier New"]delete[/font] to store temporary message arguments, however, in practice these functions contain expensive synchronisation primitives, and shouldn't be used in a performant implementation.
D13t3r
D13t3r
So the limitations of one thread are high traffic/high number of users?

I wouldn't implement the actor library myself, I already have one, which provides me with Futures, etc.
freddyscoming4you
freddyscoming4you
Basically, yes. The operations you mentioned (logging on, selecting a character, etc) are actually very short operations. With a properly configured database you can perform a single logon with a mere matter of milliseconds. Selecting a character is basically the same as all you're essentially doing is creating a session entry with an account entry and a character specification which can also be done in mere milliseconds. Given this, you can probably support at least a few hundred concurrent users. The thing about multi-threading is to take long-winded tasks and let them operate elsewhere while the rest of your code keeping on ticking. Situations where this can be seen best is in scenarios like ray tracing. Performing a simple task such as performing a logon action are almost negligent in terms of processing requirements.

At best you could set up a thread pool with an equal number of CPU cores and balance the load being sent to each core as to keep it pretty even and assign each action to a thread. You could weight operations by type such as assigning a logon as very low compared to a character selection which could be medium low since a logon is just returning true if a username and password match versus character selection where that is marginally more resources involved as you may have some joins on a few tables versus the single table operation of a login. You could then keep tabs on how much is sent to each task and balance it something ala "I've sent core 2 a few medium weighted tasks so let's use core 3 for the next dozen or so low weighted tasks."
rip-off
rip-off

So the limitations of one thread are high traffic/high number of users?
[/quote]
Essentially, yes. One thread has a peak number of users it can handle without latency becoming unreasonable. However, if your bottleneck is the disk or the network, then multi-threading won't get the result any faster. Hence the value of conducting a simulation in determining if your target hardware can meet your estimated requirements.

Also bear in mind that multi-threading isn't a performance panacea - it is perfectly possible to write a multi-threaded application that performs worse than an equivalent single threaded application.

Topic Locked

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

Sign in to reply to this topic.