sylvan wrote:
EDIT: Oh, and consider the problem of granularity. Take the example of a game. You're running a thread for an AI character who wants to perform an action on the game world atomically. How do you solve that? Do you ask the World object to retrieve whatever it
is the AI wants to access? In other words is the World object's service going to act as basically having one big lock on it for any atomic updates that AI characters want to do? Or do you put the implicit "lock" somewhere further down in the hierarchy (e.g.
on individual objects in the world)? If so, how is this different from just having locks on the individual objects? How do you solve the problem of not knowing up front which objects you want to modify (because the set of objects you need depends on the values
you find in the first couple of objects you examine)? Aren't we back in the old hornets nest of locks and condition variables again?
So basically we either have to let the toplevel object provide a transactional interface (amounting to a big implicit lock), and basically eliminate any chances of parallelism, or we go back to horribly impractical fine grained locking with all the problems
that poses. In this scenario, message passing gives no improvement to us, whereas transactional memory "just works" without any synchronisation burden placed on the programmer at all!
Ignoring the "thread" implementation idea (since the isolated units are less than threads), and other implementation assumptions, I assume you are stating the problems as: world, one big unit containing smaller pieces of mutating data, and ai characters, many
smaller units that interact with this. Let's call the units processes (not process in the OS sense). What interaction do you see between these processes?
I am not sure how you would define the interaction, but intuitively, I think you selected an example where message passing works well (and is probably why massive multiplayer games use messaging). Lets suppose each ai process receives message G to retrieve
data from the world, and sends message S to mutate the world.
Each message is queued. Assume the world has a set of messages queued, of type S. Assume it is sending out G in between S processing work.
AI1 requested data and got a message G. It sends out S based on the values in G, lets call it world state 1. In the mean time the world has changed. By the time it processes that S, it is in world state 2.
1) How can an ai reliably compute the data for S if the world is constantly changing state?
2) How much parallelism have we lost by putting so much mutable data in the large world process?
The answer to question 1 is usually in the design of the algorithms. Ideally the difference between world state 1 and 2 does not affect the validity of the ai message S to the world. That is, it may be something like, add this amount of energy to a particle,
not, set the absolute value of the energy of this particle. A delta, for a game, is probably more appropriate.
If the validity of the message is dependent upon the world state, message S could include as data a "world state stamp", that is an identifier showing that S is only valid if the world is still in state 1. Message G, from the world to the state would include
this stamp.
The world process would then ignore S messages with past due stamps, resending G to the sender ai processes with ignored S messages, so they could recompute.
This would be a contention problem with one giant world process. Which leads to the second question, granularity. If the world is broken up into many smaller processes, the contention could potentially go away, assuming every ai is not working with a world
state dependent algorithm, with each trying to modify the same small mutable state with that same algorithm.
Messaging does not make contention problems go away, it is true. You must still design the system correctly to avoid that.
I would argue messaging makes contention problems easier to identify and analyze, because now you deal only with the contention problem itself, instead of synthetic problems introduced with traditional muti-threading and locks.