Thursday, October 13, 2011

Multithreading Mayhem

While multithreading solves a lot of problems, it also causes a bunch of new ones.Whenever you change the state of the data another thread operates on, you risk that you change it while said thread reads it, which means that it will read a mixed version between old and new state.

When you expect this to happen (and chances that you forget about it are high) you can solve it with a lock. Adding a lock to the data means that one thread has to wait with reading or manipulating a piece of data until the other thread is finished.

But using locks can cause another problem: Deadlocks!

In my server I have currently two kinds of objects which have threads: ClientConnection and MapInstance. The client connection is unlikely to have problems, because the thread is only sending data to other threads, not receiving from them. Receiving data is a synchronous process. By declaring the send method as synchronized I won't have to worry about messages mixing up when multiple threads try to send data to the client at the same time.

But the map instances are a more serious problem. Their data is manipulated by a bunch of controllers they own (currently only one, but more are about to come). I am glad that processing inside the MapInstance class is single-threaded, because the controllers are processed in order and they are not supposed to communicate with the outer world except through the MapInstance class. But at any time during processing, the maps MessageDispatcher can receive a message from another thread, which is immediately passed to the controllers, which could currently be inside of their processing loop.

To prevent this from happening I could:

A) When the MessageDisptcher is delivering a message to a controller, make it check the controller if it is currently busy and when it is wait for it to finish before delivering the message. But this might cause serious delays or even deadlocks in message processing.
B) separate receiving messages from processing messages in the controllers. That way  a controller can store a message but don't actually do anything with it until its update method is called. At the beginning of its update, every controller should:
  1. Lock its message queue
  2. Copy its message queue into a buffer
  3. Unlock its message queue
  4. Process the buffered queue
The reason for copying the queue before processing it is that the processing might trigger new messages to be sent, which could be sent right back to the controller and modify the queue it is currently operating on. But when the queue is locked at that point, we might get a deadlock.

Then I just have to make sure that controllers don't communicate with the outside world except through the message queue.

No comments:

Post a Comment